题解 UVA10298 【Power Strings】
此題我寫的是后綴數組SA解法,如果不會后綴數組的可以跳過本篇blog了。
參考文獻:羅穗騫 2009集訓隊后綴數組論文
前記
最近學后綴數組,肝了不少題,也分出了后綴數組的幾個題型,看這題沒有后綴數組的解法,于是我決定來水一波。
注:思想正確,代碼不一定正確。
分析題意
給定一個字符串 L,已知這個字符串是由某個字符串 S 重復 R 次而得到的,
求 R 的最大值。
其實就是求字符串L的連續重復子串。連續重復子串就是后綴數組的一個題型。
算法分析
1、我們需要最大的R,就說明我們需要連續重復子串的子串長度最小,那我們就可以首先枚舉子串的長度k。
2、如何判斷枚舉出的子串是否符合題意,為L的連續重復子串。相信學過后綴數組的大家一定知道如何求最長公共前綴,我在這里簡要提一下。
最長公共前綴
定義&&性質
一、
Suf(i)為字符串S的從i位開始的后綴。
例:S="LAHAKIOI"
Suf(0)="LAHAKIOI".Suf(4)="AKIOI".(字符串從0開始)
二、
height[i]為Suf(SA[i])和Suf(SA[i-1])的最長公共前綴
三、
h[i]=height[rank[i]],也就是 Suf(i)和在它前一名的后綴的最長公共前
綴。
h數組有以下性質:
h[i]≥h[i-1]-1
具體證明這里不給出,如果有需要可以看我blog里的后綴數組全講。
四、
Suf(j)和Suf(k)的最長公共前綴為(height[rank[j]+1],height[rank[j]+2],height[rank[j]+3],……,height[rank[k]])的最小值。
具體證明依然不給出,有需要可以看我的blog。
正所謂我們使用后綴數組求出了SA數組和Rank數組,我們想要求最長公共前綴,就要高效求出height數組。
求height數組,我們可以根據h數組的性質(三),對于每一個后綴和他上一個的字符串,我們不需要從頭開始尋找他們的最長公共前綴,這樣時間復雜度高達O(n^2),我們可以直接從h[i-1]-1處開始判斷,因為根據h數組的性質,這兩個字符串的前h[i-1]-1位一定是相同的。
這樣時間復雜度降到O(n).
這里可能大家看的不是很懂,不過代碼比較簡單,比較容易懂:
void get_height(){ //求height數組int k=0;for(int i=0;i<n;i++){if(k)k--; //h數組可以直接用一個計數器代替int j=sa[rank[i]-1];if(rank[i]-1==-1)continue; //因為我的排名從0開始,所以排名是0時要特判while(s[i+k]==s[j+k])k++; //從k處找最長公共前綴height[rank[i]]=k; //記錄height數組} }那我們求出了最長公共前綴就可以判斷長度為k的子串是否滿足為連續最長子串。
判斷長度為k的子串是否滿足為連續最長子串
1、首先,我們要判斷字符串長度n是否能整除k,如果不能就顯然不行。
2、再看Suf(0)到Suf(k)的最長公共前綴是否為n-k。前面根據性質四,我們可以知道Suf(0)到Suf(k)的最長公共前綴,但每一次的時間都為O(n),整體下來時間復雜度十分高。
但我們可以看到Suf(0)是固定的,所以我們可以預處理出只需求出 height 數組中的每一個數到height[rank[0]]之間的最小值記為ans數組即可。
全部代碼:
#include<string> #include<stdio.h> #include<string.h> #include<iostream> #define maxn 300001 using namespace std; char s[maxn]; int n,sa[maxn],rank[maxn],newRK[maxn],key2[maxn],sum[maxn],height[maxn],k; int level; void get_sum(int m){for(int i=0;i<=m;i++) sum[i]=0;for(int i=0;i<n;i++) sum[rank[i]]++;for(int i=0;i<m;i++) sum[i]+=sum[i-1]; } bool cmp(int x,int y,int L){if(rank[x]!=rank[y])return false;if((x+L>=n&&y+L<n)||(x+L<n&&y+L>=n))return false;if(x+L>=n&& y+L>=n) return true;return rank[x+L] == rank[y+L]; } void get_height(){ //求height數組int k=0;for(int i=0;i<n;i++){if(k)k--; //h數組可以直接用一個計數器代替int j=sa[rank[i]-1];if(rank[i]-1==-1)continue; //因為我的排名從0開始,所以排名是0時要特判while(s[i+k]==s[j+k])k++; //從k處找最長公共前綴height[rank[i]]=k; //記錄height數組} } void Suffix_Sort(){ //SA板子for(int i=0;i<n;i++) rank[i]=s[i];get_sum(356);for(int i=n-1;i>=0;i--)sa[--sum[rank[i]]]=i;int w=1,m=max(n,356);while(w<n){int p=0;for(int i=n-w;i<n;i++)key2[p++]=i; //第二關鍵字越界排前for(int i=0;i<n;i++)if(sa[i]>=w)key2[p++]=sa[i]-w;//如果當前長度有第一關鍵字就記錄//以上按第二關鍵字排序get_sum(m);for(int i=n-1;i>=0;i--){int j=key2[i];sa[--sum[rank[j]]]=j;}//以上按第一關鍵字排序,直接覆蓋之前的sa數組,不需要再開一個key1newRK[sa[0]]=0;level=1;for(int i=1;i<n;i++){if(cmp(sa[i-1],sa[i],w))newRK[sa[i]]=level-1;elsenewRK[sa[i]]=level++;}for(int i=0;i<n;i++)rank[i]=newRK[i];//以上計算長度2*w的rank數組if (level==n)break;w<<=1;} } int main(){while(1){int ans[maxn];memset(sa,0,sizeof(sa));memset(height,0,sizeof(height));memset(ans,0,sizeof(ans));memset(rank,0,sizeof(rank));scanf("%s",s);n=strlen(s);if(s[0]=='.')break;Suffix_Sort();get_height();//下面求ans數組int a=rank[0],minx=10000000;for(int i=a;i>=1;i--){minx=min(minx,height[i]);ans[sa[i-1]]=minx;}minx=10000000;for(int i=a+1;i<=n;i++){minx=min(minx,height[i]);ans[sa[i]]=minx;}for(int i=1;i<=n;i++){if(n%i==0){if(ans[i]==n-i){printf("%d\n",n/i);break;}}}} }謝謝觀賞
轉載于:https://www.cnblogs.com/hyfhaha/p/10678022.html
總結
以上是生活随笔為你收集整理的题解 UVA10298 【Power Strings】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c++ string 字符串
- 下一篇: SleuthQL 一个自动化执行导出扫描