HDU - 5030 Rabbit's String(后缀数组+二分)
題目鏈接:點擊查看
題目大意:給出一個字符串,現在要求將其分為不大于k個連續的子串,對于每個子串求出字典序最大的子串,現在要求所有子串的最大子串的最大值最小,輸出這個最大子串
題目分析:最大值最小,標準的二分條件,所以我們需要想辦法二分,因為最后需要的答案是子串,我們可以用后綴數組預處理出一個前綴和,表示每個后綴貢獻的本質不同子串,這樣我們就可以二分子串的長度,也就是二分答案了,現在問題轉換為了判定問題,也就是給出一個子串,如何在分割k-1次(分割k-1次也就是分成了k塊)的情況下滿足子串的子串的最大值為當前答案
其實看了晚上的題解后自己也是有了一點小理解了,直接照著做法說吧,首先對于當前二分的位置,我們首先需要找到包含當前子串的后綴pos,這樣在這個位置之前的子串我們都無需去管了,因為字典序肯定比當前的答案要小,我們只需要將所有大于當前答案的子串都切割一下就好了,一開始我們無需切割,只需要記錄一下需要切割的位置即可,哪些位置需要切割呢?我們從pos+1開始,沿著sa數組一直往后尋找,如果lcp(pos,i)==0的話,那么當前答案一定無解,網上都是說顯然無解,我來稍微說一下為什么這樣顯然吧,因為我們當前枚舉的答案在后綴pos中一定是一個前綴,既然當前的后綴 i 與后綴 pos 的最大公共前綴為 0 ,也就是第一個字符都不相同,加上我們又是沿著sa數組升序往后找的,這就只能說明一個問題,那就是后綴 i 的首字母就比當前枚舉的答案要大了,無論如何分割,最后的最大子串一定不會小于這個首字母了,所以當前的答案必定不可能符合條件了,如此一來我們必須保證lcp(pos,i)始終大于零才行,這樣我們每次在[sa[i],sa[i]+len-1]中隨便選一個點切割,就能使得當前后綴中不會再出現比當前二分的答案字典序還要大的答案了,列出所有的區間后,我們對其排序,以貪心的思想將割點最小化,最后再和k-1比較就能完成check函數了
這里還有一個細節需要注意一下,我們儲存的是割點的區間,這個直接來儲存的話不太好轉換,但是我們不妨儲存每個區間的首端點,因為題目要求分成的k個區間連續,所以第一個區間的首端點一定是 0 了,無需我們儲存,我們只需要儲存后續的k-1個區間的首端點就好了
代碼:
#include<iostream> #include<cstdio> #include<string> #include<ctime> #include<cstring> #include<algorithm> #include<stack> #include<queue> #include<map> #include<set> #include<cmath> #include<sstream> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=1e5+100;char str[N];int sa[N]; //SA數組,表示將S的n個后綴從小到大排序后把排好序的 //的后綴的開頭位置順次放入SA中 int t1[N],t2[N],c[N];int rk[N],height[N],len,k;int s[N];LL sum[N];void build_sa(int s[],int n,int m)//n為添加0后的總長 {int i,j,p,*x=t1,*y=t2;for(i=0;i<m;i++) c[i]=0;for(i=0;i<n;i++) c[x[i]=s[i]]++;for(i=1;i<m;i++) c[i]+=c[i-1];for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;for(j=1;j<=n;j<<=1) {p=0;for(i=n-j;i<n;i++) y[p++]=i;for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;for(i=0;i<m;i++) c[i]=0;for(i=0;i<n;i++) c[x[y[i]]]++;for(i=1;i<m;i++) c[i]+=c[i-1];for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];swap(x,y);p=1,x[sa[0]]=0;for(i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++;if(p>=n) break;m=p;} }void get_height(int s[],int n)//n為添加0后的總長 {int i,j,k=0;for(i=0;i<=n;i++)rk[sa[i]]=i;for(i=0;i<n;i++) {if(k) k--;j=sa[rk[i]-1];while(s[i+k]==s[j+k]) k++;height[rk[i]]=k;} }void solve(int base=128) {build_sa(s,len+1,base);get_height(s,len); }struct Node {int l,r;Node(int L,int R){l=L,r=R;}bool operator<(const Node& a)const{if(r==a.r)return l<a.l;return r<a.r;} };bool check(LL mid) {int pos=lower_bound(sum+1,sum+1+len,mid)-sum;//定位sa數組 int length=mid-sum[pos-1]+height[pos];//確定串長vector<Node>cut;//記錄割點if(len-sa[pos]>length)//如果對于當前后綴中還存在比二分的答案還要大的子串,需要割掉cut.push_back(Node(sa[pos],sa[pos]+length-1));for(int i=pos+1;i<=len;i++)//從 pos+1 到 n 尋找哪些需要割掉{length=min(length,height[i]);if(!length)return false;cut.push_back(Node(sa[i],sa[i]+length-1));//添加割點所在的區間}sort(cut.begin(),cut.end());//排序int cnt=0;int p=-1;for(int i=0;i<cut.size();i++)//貪心找最少的割點{if(cut[i].l>p){p=cut[i].r;cnt++;}}return cnt<k; }int main() { // freopen("input.txt","r",stdin); // ios::sync_with_stdio(false);while(scanf("%d",&k)!=EOF&&k){scanf("%s",str);len=strlen(str);for(int i=0;i<len;i++)s[i]=str[i]-'a'+1;s[len]=0;solve();for(int i=1;i<=len;i++)sum[i]=sum[i-1]+len-sa[i]-height[i];LL l=1,r=sum[len],ans,pos,length;while(l<=r)//二分答案,也就是字符串{LL mid=l+r>>1;if(check(mid)){ans=mid;r=mid-1;}elsel=mid+1;}int j=lower_bound(sum+1,sum+1+len,ans)-sum;//獲得第ans個子串的所屬后綴pos=sa[j];//找到后綴length=ans-sum[j-1]+height[j];//計算第ans個子串的長度for(int i=pos;i<pos+length;i++)putchar(str[i]);putchar('\n');}return 0; }?
總結
以上是生活随笔為你收集整理的HDU - 5030 Rabbit's String(后缀数组+二分)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HDU - 5008 Boring St
- 下一篇: SPOJ - PHRASES Relev