hdu 2459
題意:讓你求一個串中連續重復次數最多的串(不重疊),如果重復的次數一樣多的話就輸出字典序小的那一串。
我的解題思路:這個題目很明顯就是后綴數組的運用,我首先是枚舉長度,然后判斷該長度下是否可以找到重復子串。關鍵是如何來找,我是這樣算的,首先判斷sa[i-1]和sa[i]的最長公共前綴,如果長度大于等于枚舉的那個長度,就說明有可能這兩個串是有重復的部分,接下來看這兩個串的位置差,如果也等于枚舉長度,說明兩個串的重復部分是緊挨在一塊的,那么個數+1,并且記錄下重復串中,循環節的最后的起始位置。字典序的判斷這里就直接利用rank數組,比較兩個不同循環節起始位置的rank值。
但這樣的思路不知道哪里錯了,在討論區里面測試了很多代碼都對了,在poj上交是WA,在hdu上交是TLE,我想用二分長度去算,可是變成了WA啦。。真不清楚哪里的問題。。
我的代碼:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;const int maxn = 100005;
int s[maxn],t[maxn],t2[maxn],c[maxn];
int rk[maxn],height[maxn],sa[maxn],ans,pm;
char str[maxn];void getsa(int n,int m)
{int i,*x = t,*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(int k = 1; k <= n; k = k << 1){int p = 0;for(i = n-k; i < n; i++) y[p++] = i;for(i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i]-k;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]] == y[sa[i-1]] && y[sa[i]+k] == y[sa[i-1]+k] ? p-1:p++;if(p >= n) break;m = p;}
}void getheight(int n)
{int i,j,k = 0;for(i = 1; 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;}
}bool find(int n,int m)
{int mint,maxt,p = -1,cnt = 1;bool flag = false;for(int i = 1; i <= n; i++){int t = height[i];if(t >= m){mint = min(sa[i],sa[i-1]);maxt = max(sa[i],sa[i-1]); if(mint + m == maxt) //如果按字典序排列的相鄰兩個后綴的位置差為m,那么肯定是重復的。{cnt++;p = max(p,maxt); //p不斷的尋找最后那個循環節的位置。}else {if(cnt == 1) continue; //cnt = 1,說明沒有符合條件的重復子串if(cnt > ans) //cnt > ans 毫無疑問,這個肯定是比當前更優的結果,先保存下來。{ans = cnt;pm = p;flag = true; //flag=true,說明當重復的循環節長度為m時,確實能夠找到這樣的子串。}else if(cnt == ans && rk[p] < rk[pm]) //cnt = ans,那么就要比較兩者的字典序大小,這里直接拿rank數組比較{pm = p; //pm表示的是這一個重復子串當中,最后的循環節的起始位置。flag = true;}cnt = 1;p = -1; //cnt = 1,p = -1,這一個子串算是找完了,看能不能找到別的子串。}}else //理由同上,這里的代碼和上面的小部分一樣,但是處理的場合不同。{if(cnt == 1) continue;if(cnt > ans){ans = cnt;pm = p;flag = true;}else if(cnt == ans && rk[p] < rk[pm]){pm = p;flag = true;}cnt = 1; p = -1; //cnt = 1,p = -1,這一個子串算是找完了,看能不能找到別的子串。}}return flag;
}int main()
{ int cas = 1;while(cin>>str){int n = 0;if(str[0] == '#') break;for(int i = 0; str[i] != '\0'; i++)s[n++] = str[i];s[n] = 0;getsa(n+1,300);getheight(n);int len = -1;ans = 0;for(int i = 1; i <= n/2; i++){if(find(n,i) == true)len = i;}printf("Case %d: ",cas++);if(len == -1){printf("%c\n",str[sa[1]]);continue;}for(int i = pm-(ans-1)*len; i < pm+len; i++)printf("%c",str[i]);printf("\n");}return 0;
}
截取別人的思路:假設一個長度為l的子串重復出現兩次,那么它必然會包含s[0]、s[l]、s[l*2]...之中的相鄰的兩個。不難看出,該重復子串必然會包含s[0..l]或s[l..l*2]或s[l*2..l*3]...。所以,我們可以枚舉一個i,對于每個i*l的位置,利用后綴數組可以求出s[i*l..(i+1)*l]向后延伸的長度k。k/l+1即i*l..(i+1)*l這一段重復出現的次數。但還有一種情況。考慮以下的字符串:
截取別人的思路:假設一個長度為l的子串重復出現兩次,那么它必然會包含s[0]、s[l]、s[l*2]...之中的相鄰的兩個。不難看出,該重復子串必然會包含s[0..l]或s[l..l*2]或s[l*2..l*3]...。所以,我們可以枚舉一個i,對于每個i*l的位置,利用后綴數組可以求出s[i*l..(i+1)*l]向后延伸的長度k。k/l+1即i*l..(i+1)*l這一段重復出現的次數。但還有一種情況。考慮以下的字符串:
aababababab
假設現在l=2,i=1。則當前得到的子串為ba.用后綴數組可以求得k=7,則ba共重復出現了4次。但實際上,長度為2的子串重復出現最多的應該是“ab”,出現了5次。可以看出來,上述方法求得的k不能整除l,故可能在i的左邊位置存在一個子串能完整重復覆蓋i這個子串后面的子串。這里是i左邊一位的“ab”子串。分析下這種情況,可以得知,如果以i-(l-k%l)開頭的長度為l的子串,向后延伸的長度能大于k的話,那么有一個子串出現次數為k/l+2。就這樣,問題得到解決。
AC: #include<cstdio> #include<cstring> #include<iostream> #include<cmath> using namespace std; #define N 100005 int ws1[N],wv[N],wa[N],wb[N]; int rank[N],height[N],sa[N],len; char str[N],xiao; int dp[N][25];int min(int x,int y) {return x<y?x:y; }int cmp(int *r,int a,int b,int l) {return r[a]==r[b] && r[a+l]==r[b+l]; }void da(char *r,int *sa,int n,int m) {int i,j,p,*x=wa,*y=wb,*t;for(i=0;i<m;i++)ws1[i]=0;for(i=0;i<n;i++)ws1[x[i]=r[i]]++;for(i=1;i<m;i++)ws1[i]+=ws1[i-1];for(i=n-1;i>=0;i--)sa[--ws1[x[i]]]=i;for(j=1,p=1;p<n;j*=2,m=p){for(p=0,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<n;i++)wv[i]=x[y[i]];for(i=0;i<m;i++)ws1[i]=0;for(i=0;i<n;i++)ws1[wv[i]]++;for(i=1;i<m;i++)ws1[i]+=ws1[i-1];for(i=n-1;i>=0;i--)sa[--ws1[wv[i]]]=y[i];for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;} }void calheight(char *r,int *sa,int n) {int i,j,k=0;for(i=1;i<=n;i++)rank[sa[i]]=i;for(i=0;i<n;height[rank[i++]]=k)for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++) ; }void RMQ()//RMQ初始化 {int i,j,m;m=(int)(log((double)len)/log(2.00));for(i=1;i<=len;i++)dp[i][0]=height[i];for(j=1;j<=m;j++)for(i=1;i+(1<<j)-1<=len;i++)dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]); }int lcp(int x,int y)//求最長公共前綴 {int t;x=rank[x];y=rank[y];if(x>y)swap(x,y);x++;t=(int)(log(double(y-x+1))/log(2.00));return min(dp[x][t],dp[y-(1<<t)+1][t]); }void solve() {int i,j,max=1,f=0,l1,num=0,t,node=1,k,cnt,p;for(i=1;i<=len/2;i++)//i<=len/2優化了,枚舉長度不同的循環節{for(j=0;j+i<len;j+=i){if(str[j]!=str[j+i])//這里也優化了continue;l1=lcp(j,j+i);num=l1/i+1;p=j;t=i-l1%i;cnt=0;for(k=j-1;k>=0&&k+i>j&&str[k]==str[k+i];k--)//這個for循環我也不是很理解,但是大體的意思明白{cnt++;if(cnt==t){num++;p=k;}else if(rank[k]<rank[p])p=k;}if(max<num){f=p;max=num;node=i;}else if(max==num&&rank[f]>rank[p]){f=p;node=i;}}}if(max==1){printf("%c\n",xiao);return ;}for(i=f;i<=f+max*node-1;i++)printf("%c",str[i]);printf("\n"); } int main() {int T=0,i;while(scanf("%s",str)!=EOF&&str[0]!='#'){T++;len=strlen(str);xiao='z'+1;for(i=0;i<len;i++)if(str[i]<xiao)xiao=str[i];str[len]='0';da(str,sa,len+1,'z'+1);calheight(str,sa,len);RMQ();printf("Case %d: ",T);solve();}return 0; }總結
- 上一篇: jeewx-api 1.0.5 版本发布
- 下一篇: hdu 4565