1. 程式人生 > >【POJ2406】

【POJ2406】

(http://www.elijahqi.win/2017/07/13/%E3%80%90poj2406%E3%80%91/)
Power Strings
Time Limit: 3000MS Memory Limit: 65536K
Total Submissions: 48867 Accepted: 20366
Description

Given two strings a and b we define a*b to be their concatenation. For example, if a = “abc” and b = “def” then a*b = “abcdef”. If we think of concatenation as multiplication, exponentiation by a non-negative integer is defined in the normal way: a^0 = “” (the empty string) and a^(n+1) = a*(a^n).
Input

Each test case is a line of input representing s, a string of printable characters. The length of s will be at least 1 and will not exceed 1 million characters. A line containing a period follows the last test case.
Output

For each s you should print the largest n such that s = a^n for some string a.
Sample Input

abcd
aaaa
ababab
.
Sample Output

1
4
3
Hint

This problem has huge input, use scanf instead of cin to avoid time limit exceed.

據說字尾陣列倍增+rmq都超時了,我寫完之後發現也超時了,不過證明後綴陣列寫的還算對,kmp留待填坑

首先構造sa,height等陣列 height表示排名第i和排名i-1陣列lcp是多少
增設rm陣列 rm[排名]=與整個字串的lcp
判斷時,如果i不能被n整除那麼也就不可能成為迴圈節 如果suffix[i+1]可以和整個字串匹配,且匹配長度正好等於suffix[i+1]那麼說明 迴圈節是n/i;

#include<cstdio>
#include<cstring>
#define N 1100000
int rank[N<<1],rank1[N],st[N],count[N],tmp[N],sa[N],k,height[N],rm[N];
char a[N];
int main(){
    freopen("poj2406.in","r",stdin);
    freopen("poj2406.out","w",stdout);
    while (1){
        scanf("%s",a+1);
        if (a[1]=='.') break;
        memset(st,0,sizeof(st));
        memset(rank,0,sizeof(rank));
        memset(rank1,0,sizeof(rank1));
        int n=strlen(a+1);
        for (int i=1;i<=n;++i) st[a[i]]=1;
        for (int i=1;i<=255;++i) st[i]+=st[i-1];
        for (int i=1;i<=n;++i) rank[i]=st[a[i]];
        k=0;
        for (int p=1;k!=n;p+=p){
            memset(count,0,sizeof(count));
            for (int i=1;i<=n;++i) count[rank[i+p]]++;
            for (int i=1;i<=n;++i) count[i]+=count[i-1];
            for (int i=n;i>=1;--i) tmp[count[rank[i+p]]--]=i;
            memset(count,0,sizeof(count));
            for (int i=1;i<=n;++i) count[rank[tmp[i]]]++;
            for (int i=1;i<=n;++i) count[i]+=count[i-1];
            for (int i=n;i>=1;--i) sa[count[rank[tmp[i]]]--]=tmp[i];
            memcpy(rank1,rank,sizeof(rank)>>1);
            rank[sa[1]]=k=1;
            for (int i=2;i<=n;++i){
                if (rank1[sa[i]]!=rank1[sa[i-1]]||rank1[sa[i]+p]!=rank1[sa[i-1]+p]) ++k;
                rank[sa[i]]=k;
            }
        }
    //  for (int i=1;i<=n;++i) printf("%d ",sa[i]);printf("\n");
        k=0;
        for (int i=1;i<=n;++i){
            if (rank[i]==1) {
                height[rank[i]]=0;continue;
            }
            k=k==0?0:k-1;
            while (a[i+k]==a[sa[rank[i]-1]+k])++k;
            height[rank[i]]=k;
        }
        //for (int i=1;i<=n;++i) printf("%d ",height[i]);printf("\n");
        k=rank[1];
        rm[k]=1000000;
        for (int i=k-1;i>=0;--i)
            if (height[i+1]<rm[i+1]) rm[i]=height[i+1];else rm[i]=rm[i+1];
        for (int i=k+1;i<=n;++i)
            if (height[i-1]<rm[i-1]) rm[i]=height[i];else rm[i]=rm[i-1];
        //for (int i=1;i<=n;++i) printf("%d ",rm[i]);
        bool flag=false;
        for (int i=1;i<=n/2;++i){
            if (n%i) continue;
            if (rm[rank[i+1]]==n-i) {
                printf("%d\n",n/i);flag=true;break;
            }
        }
        if (flag==false )printf("1\n");
    }
    return 0;
}

在做kmp之前首先還要複習一下kmp的定義包括求法等

kmp:給定兩個字串如T,S求 S在T 中能否匹配能的話返回匹配的位置

我們首先用一個圖來描述kmp演算法的思想。在字串S中尋找T,當匹配到位置i時兩個字串不相等,這時我們需要將字串T向前移動。常規方法是每次向前移動一位,但是它沒有考慮前i-1位已經比較過這個事實,所以效率不高。事實上,如果我們提前計算某些資訊,就有可能一次前移多位。假設我們根據已經獲得的資訊知道可以前移k位,我們分析移位前後的T有什麼特點。我們可以得到如下的結論:

A段字串是T的一個字首。
B段字串是T的一個字尾。
A段字串和B段字串相等。
所以前移k位之後,可以繼續比較位置i的前提是T的前i-1個位置滿足:長度為i-k-1的字首A和字尾B相同。只有這樣,我們才可以前移k位後從新的位置繼續比較。

圖片中 O代表S,f代表T

所以kmp演算法的核心即是計算字串f每一個位置之前的字串的字首和字尾公共部分的最大長度(不包括字串本身,否則最大長度始終是字串本身)。獲得f每一個位置的最大公共長度之後,就可以利用該最大公共長度快速和字串O比較。當每次比較到兩個字串的字元不同時,我們就可以根據最大公共長度將字串f向前移動(已匹配長度-最大公共長度)位,接著繼續比較下一個位置。事實上,字串f的前移只是概念上的前移,只要我們在比較的時候從最大公共長度之後比較f和O即可達到字串f前移的目的。

next陣列計算
這個最大公共長度在演算法導論裡面被記為next陣列

在這裡要注意一點,next陣列表示的是長度,下標從1開始;但是在遍歷原字串時,下標還是從0開始。

在求得next陣列後例如
ababab next[6] = 4; 即

ababab
ababab
1~4位 與2~6位是相同的

那麼前兩位
就等於3、4位
3、4位就等於5、6位
……
所以 如果 能整除 也就迴圈到最後了

#include<cstdio>
#include<cstring>
#define N 1100000
int next[N];
char str1[N];
int main(){
    freopen("poj2406.in","r",stdin);
    freopen("poj2406.out","w",stdout);
    while (1){
        scanf("%s",str1);
        if (str1[0]=='.') break;
        int n=strlen(str1);
        //get_next
        int i=0,j=-1;next[0]=-1;
        while (i<n){
            if (j==-1||str1[i]==str1[j]){
                ++i,++j;next[i]=j;
            }else j=next[j];
        }
        //for (int i=0;i<=n;++i) printf("%d ",next[i]);printf("\n");
        int ans=1;
        if (n%(n-next[n])==0) ans=n/(n-next[n]);
        printf("%d\n",ans);
    }
    return 0;
}