后綴數組專題與代碼模板


 

后綴數組 DA(倍增)算法求 SA[N] 與 Rank[N] (時間O(NlogN),空間O(N))

 

sa[i] : 表示 排在第i位的后綴 起始下標

rank[i] : 表示后綴 suffix(i)排在第幾

height[i] : 表示 sa[i-1] 與 sa[i] 的LCP 值

h[i]: 表示 suffix(i)與其排名前一位的 LCP值

const int N = int(2e5)+10;
int cmp(int *r,int a,int b,int l){
    return (r[a]==r[b]) && (r[a+l]==r[b+l]);
}
// 用於比較第一關鍵字與第二關鍵字,
// 比較特殊的地方是,預處理的時候,r[n]=0(小於前面出現過的字符)

int wa[N],wb[N],ws[N],wv[N];
int rank[N],height[N];
void DA(int *r,int *sa,int n,int m){ //此處N比輸入的N要多1,為人工添加的一個字符,用於避免CMP時越界
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i; //預處理長度為1
    for(j=1,p=1;p<n;j*=2,m=p) //通過已經求出的長度J的SA,來求2*J的SA
    {
        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; //利用長度J的,按第二關鍵字排序
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<m;i++) ws[i]=0;
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[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++;  //更新名次數組x[],注意判定相同的
    }
}

void calheight(int *r,int *sa,int n){ // 此處N為實際長度
    int i,j,k=0;        // height[]的合法范圍為 1-N, 其中0是結尾加入的字符
    for(i=1;i<=n;i++) rank[sa[i]]=i;  // 根據SA求RANK
    for(i=0;i<n; height[rank[i++]] = k ) // 定義:h[i] = height[ rank[i] ]
    for(k?k--:0,j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++); //根據 h[i] >= h[i-1]-1 來優化計算height過程
}

char str[N];
int sa[N];
int main(){
    char str[N];
    scanf("%s",str);
    int n = strlen(str);
    str[n]=0;
    
    da(str,sa,n+1,128);  //注意區分此處為n+1,因為添加了一個結尾字符用於區別比較
    calheight(str,sa,n);
}

 

DC3 模板 (  時間復雜度O(N),空間復雜度O(3N) )

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = int(3e6)+10;
const int N = maxn;

    #define F(x) ((x)/3+((x)%3==1?0:tb))
    #define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
    int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
    int c0(int *r,int a,int b)
    {return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];}
    int c12(int k,int *r,int a,int b)
    {if(k==2) return r[a]<r[b]||r[a]==r[b]&&c12(1,r,a+1,b+1);
    else return r[a]<r[b]||r[a]==r[b]&&wv[a+1]<wv[b+1];}
    void sort(int *r,int *a,int *b,int n,int m)
    {
        int i;
        for(i=0;i<n;i++) wv[i]=r[a[i]];
        for(i=0;i<m;i++) ws[i]=0;
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) b[--ws[wv[i]]]=a[i];
        return;
    }
    void dc3(int *r,int *sa,int n,int m) //涵義與DA 相同
    {
        int i,j,*rn=r+n,*san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
        r[n]=r[n+1]=0;
        for(i=0;i<n;i++) if(i%3!=0) wa[tbc++]=i;
        sort(r+2,wa,wb,tbc,m);
        sort(r+1,wb,wa,tbc,m);
        sort(r,wa,wb,tbc,m);
        for(p=1,rn[F(wb[0])]=0,i=1;i<tbc;i++)
        rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
        if(p<tbc) dc3(rn,san,tbc,p);
        else for(i=0;i<tbc;i++) san[rn[i]]=i;
        for(i=0;i<tbc;i++) if(san[i]<tb) wb[ta++]=san[i]*3;
        if(n%3==1) wb[ta++]=n-1;
        sort(r,wb,wa,ta,m);
        for(i=0;i<tbc;i++) wv[wb[i]=G(san[i])]=i;
        for(i=0,j=0,p=0;i<ta && j<tbc;p++)
        sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
        for(;i<ta;p++) sa[p]=wa[i++];
        for(;j<tbc;p++) sa[p]=wb[j++];
        return;
    }

 

訓練題

  重復(出現)子串

    1.可重疊

      根據 height[i] = LCP{ suffix( sa[i-1] ), suffix( sa[i] ) } , 我們知道最長重復可重疊子串長度其實就是 Max{ height[i] }

   2.不可重疊

      poj 1743 Musical Theme

      重復出現的子串不能有重疊, 假定我們需要找一個長度為K,且不重疊的子串。 我們可以講求出的height數組從1-n按其大於等於K進行分組,相同分組

中 Max{ SAi } - Max{ SAj } > = k , 則存在滿足要求的方案。

      對於本題,還需要預處理一些問題。 需要兩個序列的差值相同,我們可以轉換成前后的差值,然后將N個點的信息,收縮成N-1個段信息。然后就可以

用模板做了。然后二分判定即可。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
using namespace std;

const int N = int(2e5)+10;

int cmp(int *r,int a,int b,int l){
    return (r[a]==r[b]) && (r[a+l]==r[b+l]);
}
int wa[N],wb[N],ws[N],wv[N];
int rank[N],height[N];
void DA(int *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[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++) ws[i]=0;
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[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++;
        //printf("p = %d\n", p );
    }
}
void calheight(int *r,int *sa,int n){
  //  memset(height,0,sizeof(height));
  //  memset(rank,0,sizeof(rank));
    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++);
}

int a[N], sa[N], n;

vector<int> S[N];

bool check(int k){
    bool flag = false;
    int cur = -1;
    for(int i = 1; i <= n; i++)
    {
        if( height[i] < k ) S[++cur].clear();
        S[cur].push_back(i);
    }
    for(int i = 0;i <= cur; i++)
    {
        int Max=-1,Min=N;
        if( S[i].size() > 1 ){
            for(int j = 0; j < S[i].size(); j++)
            {
                Max = max( Max, sa[ S[i][j] ] );
                Min = min( Min, sa[ S[i][j] ] );
            }
            if( Max-Min >= k ){flag = true; break;}
        }
    }
    return flag;
}
int solve(){
    DA(a,sa,n+1,200);
    calheight(a,sa,n);
    int l = 0, r = n, res = 0;
    while(l<=r)
    {
        int m = (l+r)>>1;
        if( check(m) ) res=m,l=m+1;
        else r = m-1;
    }
    return res>=4?res+1:0;
}
int main(){
    while( scanf("%d",&n), n )
    {
        for(int i = 0; i < n; i++)
            scanf("%d",&a[i]);
        for(int i = 0; i < n-1; i++)
            a[i] = a[i+1]-a[i]+90;
        a[--n] = 0;
        int ans = solve();
        printf("%d\n", ans );
    }
    return 0;
}
View Code

 

        poj 3261 可重疊的K次最長重復子串
      我們做完后綴數組,得到height[]數組后,前綴相同的都相鄰,且具有傳遞性。 
      因為 height[i] 表示 suffix( sa[i-1] ) 與 suffix( sa[i] )的 LCP, 我們統計出所有連續長度為K中的最小LCP,然后對所有最小LCP取最大值即為答案。這里可以用單調隊列維護。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<map>
using namespace std;

const int N = (int)2e5+10;

int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int wa[N],wb[N],ws[N],wv[N];
int height[N], rank[N];

void da(int *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[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<m;i++) ws[i]=0;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
        for(swap(x,y),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(int *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[j+k]==r[i+k];k++);
}
int Q[N], sa[N];

void solve(int *r,int n,int m,int K)
{
    da(r,sa,n+1,m);
    calheight(r,sa,n);
    int ql = 0, qr = 0, Max = 0;
    for(int i = 2;i <= n; i++)
    {
        while( (qr>ql) && ( height[Q[qr-1]] >= height[i] ) ) qr--;
        Q[qr++] = i;
        if( i > K+1 ) while( (ql<qr) && (Q[ql]<=(i-K)) ) ql++;
        if( i >= K+1 )  Max = max( Max, height[Q[ql]] );
    }

    printf("%d\n", Max);
}

int r[N], n, K;
int tmp[N];
map<int,int> mp;

int main(){
    while( scanf("%d%d",&n,&K) != EOF)
    {
        for(int i = 0; i < n; i++)
        {
            scanf("%d",&r[i]);
            tmp[i] = r[i];
        }
        if( n == 1 && K == 1 ){ puts("1"); continue; }
        sort(tmp,tmp+n);
        int sz = unique(tmp,tmp+n)-tmp;
        mp.clear();
        for(int i = 0;i < sz; i++)
            mp[ tmp[i] ] = i+1;
        for(int i = 0; i < n; i++)
            r[i] = mp[ r[i] ];
        r[n] = 0;
        solve( r, n, sz+1, K-1 );
    }
    return 0;
}
View Code

  

  子串的個數

      spoj 694  不相同的子串個數,可以重疊。

      一個串S,其所有子串 Si, 必定是某個后綴 Suffix(k)的一個前綴。 我們依據此將問題轉換成求所有后綴的,不相同前綴數量。

      將所有后綴按照  suffix( sa[1] ), suffix( sa[2] ), ..., suffix( sa[n] ) 依次放入然后計算。

      當一個串 S(有效范圍 [1,n]), 則當一個后綴 suffix( sa[i] ) 加入進來, 其會產生 n-sa[i]+1 個前綴,並且會有height[i]個與前一個相同。

      所以加入當前后綴增加的不同前綴數量為:  n-sa[i]+1 - height[i].   然后整個串的不同子串數量即為,所有前綴產生的總和。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 1010;

int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int wa[N],wb[N],ws[N],wv[N];
int height[N], rank[N];

void da(int *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[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<m;i++) ws[i]=0;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
        for(swap(x,y),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(int *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[j+k]==r[i+k];k++);
}

char str[N];
int sa[N], n, r[N];

void solve()
{
    da(r,sa,n+1,128);
    calheight(r,sa,n);
    int ans = 0;
    for(int i = 1; i <= n; i++)
        ans += n-sa[i]-height[i];
    printf("%d\n", ans );
}
int main(){
    int T;
    scanf("%d",&T);
    while( T-- )
    {
        scanf("%s", str);
        n = strlen(str);
        for(int i = 0; i < n; i++)
            r[i] = (int)str[i];
        r[n]=0;
        solve();
    }
    return 0;
}
View Code

       

      spoj 705 如上題一樣,只是N擴大到了10^5, 上題N=1000,可以用DP來做

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 51010;

int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int wa[N],wb[N],ws[N],wv[N];
int height[N], rank[N];

void da(int *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[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<m;i++) ws[i]=0;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
        for(swap(x,y),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(int *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[j+k]==r[i+k];k++);
}

char str[N];
int sa[N], n, r[N];

void solve()
{
    da(r,sa,n+1,128);
    calheight(r,sa,n);
    int ans = 0;
    for(int i = 1; i <= n; i++)
        ans += n-sa[i]-height[i];
    printf("%d\n", ans );
}
int main(){
    int T;
    scanf("%d",&T);
    while( T-- )
    {
        scanf("%s", str);
        n = strlen(str);
        for(int i = 0; i < n; i++)
            r[i] = (int)str[i];
        r[n]=0;
        solve();
    }
    return 0;
}
View Code

 

  回文子串

      URAL 1297 將原串S,與倒序串S1,連接起來,連接出插入一特殊字符。

      分奇數和偶數枚舉對稱中心, 對於奇數 Lcp( rank[i], rank[ 2*n-i ] ). 對於偶數 Lcp( rank[i], rank[2*n+1-i] )

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;

const int N = 2024;


int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int wa[N],wb[N],ws[N],wv[N];
int height[N], rank[N];

void da(int *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[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<m;i++) ws[i]=0;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
        for(swap(x,y),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(int *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[j+k]==r[i+k];k++);
}

char str[N];
int r[N], sa[N], n;
int dp[N][20];

void InitRMQ(){
    int cnt = n+n+1;
    for(int i = 1; i <= cnt; i++)
        dp[i][0] = height[i];
    for(int i = 1; (1<<i) <= cnt; i++)
    {
        for(int j = 1; j+(1<<i)-1 <= cnt; j++)
            dp[j][i] = min( dp[j][i-1], dp[j+(1<<(i-1))][i-1] );
    }
}
int query(int L,int R){
    if(L > R) swap(L,R); L++;
    int k = (int)floor(  log(1.*(R-L+1)) / log(2.0) );
    int t = min( dp[L][k], dp[R-(1<<k)+1][k] );
    return t;
}
void solve(){
    int cnt = n+n+1;
    da(r,sa,cnt+1,128);
    calheight(r,sa,cnt);

    /*printf("n = %d\n", n );
    for(int i = 1; i <= cnt; i++)
        printf("height[%d] = %d\n", i, height[i]);
    for(int i = 0; i <= cnt; i++)
        printf("rank[%d] = %d, sa[%d] = %d\n", i,rank[i],i,sa[i] );*/
    InitRMQ();
    int Max1 = 0, p1;
    // odd
    for(int i = 0; i < n; i++){
        int t = query( rank[i], rank[n+n-i] );
        if( t > Max1 ) Max1 = t, p1 = i;
    }

    int Max2 = 0, p2;
    // even
    for(int i = 1;i < n; i++){
        int t = query( rank[i], rank[n+n-i+1] );
        if( t > Max2 ) Max2 = t, p2 = i;
    }

//    printf("Max1=%d,p1=%d, Max2=%d,p2=%d\n",Max1,p1,Max2,p2);
    if( 2*Max1-1 >= 2*Max2 ){
        for(int j = p1-Max1+1; j <= p1+Max1-1; j++)
            printf("%c",str[j]); puts("");
    }
    else{
        for(int j = p2-Max2; j <= p2+Max2-1; j++)
            printf("%c", str[j]); puts("");
    }
}
int main(){

//    printf("%lf\n", log(5.0)/log(2.0) );
    while( scanf("%s",str) != EOF)
    {
        n = strlen(str);
        for(int i = 0; i < n; i++)
            r[i] = (int)str[i];
        r[n] = 1;
        int t = n;
        for(int i = n-1; i >= 0; i--)
            r[++t] = (int)str[i];
        r[2*n+1] = 0;

       // for(int i = 0; i <= 2*n; i++)
       //     printf("r[%d] = %d\n", i, r[i] );
        solve();
    }
    return 0;
}
View Code

 

   連續重復子串

      poj 2406 雖然此題用KMP的nxt函數來做可能更合適點,不過我們還是用后綴數組來做一次,更熟悉后綴數組的作用。

      做法其實很簡單,枚舉長度L,若 a^k = S, 那么 顯然 Lcp{ rank[0], rank[L] } = n-L.  因為LCP總是與rank[0]比,可以預處理下,這樣空間復雜度就降下來了,不過N= 1e6,  DA的NlogN的預處理會TLE,需要用 DC3的O(N)才能勉強 2700ms 過。  KMP 100Ms就能跑過去。。。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
const int N = int(1e6)+10;
int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int wa[N],wb[N],ws[N],wv[N];
int height[N], rank[N];
void da(int n,int m){
    int i,j,p,*x=wa,*y=wb;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[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<m;i++) ws[i]=0;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
        for(swap(x,y),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(int *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[j+k]==r[i+k];k++);
}

int sa[N],r[N], n, dp[N];
char str[N];

void InitRMQ(){
    int x = rank[0]; dp[x] = n;
    for(int i = x-1; i >= 1; i--)
        dp[i] = min( dp[i+1], height[i+1] );
    for(int i = x+1; i <= n; i++)
        dp[i] = min( dp[i-1], height[i] );
}
vector<int> S;
int main(){
    while( scanf("%s", str) != EOF )
    {
        if( strcmp(str,".") == 0 ) break;
        n = strlen(str);
        for(int i = 0; i < n; i++)
            r[i] = str[i];
        r[n] = 0;

        da(r,sa,n+1,128);
        calheight(r,sa,n);

        InitRMQ();
        S.clear();
        for(int i = 1; i*i <= n; i++)
        {
            if(n%i==0){
                S.push_back(i);
                if( i*i != n ) S.push_back(n/i);
            }
        }
        sort(S.begin(),S.end());
        for(vector<int>::iterator it = S.begin(); it != S.end(); it++ ){
            int L = *it;
            if( n%L == 0 ){
                int t = dp[rank[L]]; //query( rank[0], rank[L] );
                if( t == n-L ){
                     printf("%d\n", n/L );
                     break;
                }
            }
        }
    }
    return 0;
}
DA
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = int(3e6)+10;
const int N = maxn;

    #define F(x) ((x)/3+((x)%3==1?0:tb))
    #define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
    int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
    int c0(int *r,int a,int b)
    {return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];}
    int c12(int k,int *r,int a,int b)
    {if(k==2) return r[a]<r[b]||r[a]==r[b]&&c12(1,r,a+1,b+1);
    else return r[a]<r[b]||r[a]==r[b]&&wv[a+1]<wv[b+1];}
    void sort(int *r,int *a,int *b,int n,int m)
    {
        int i;
        for(i=0;i<n;i++) wv[i]=r[a[i]];
        for(i=0;i<m;i++) ws[i]=0;
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) b[--ws[wv[i]]]=a[i];
        return;
    }
    void dc3(int *r,int *sa,int n,int m)
    {
        int i,j,*rn=r+n,*san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
        r[n]=r[n+1]=0;
        for(i=0;i<n;i++) if(i%3!=0) wa[tbc++]=i;
        sort(r+2,wa,wb,tbc,m);
        sort(r+1,wb,wa,tbc,m);
        sort(r,wa,wb,tbc,m);
        for(p=1,rn[F(wb[0])]=0,i=1;i<tbc;i++)
        rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
        if(p<tbc) dc3(rn,san,tbc,p);
        else for(i=0;i<tbc;i++) san[rn[i]]=i;
        for(i=0;i<tbc;i++) if(san[i]<tb) wb[ta++]=san[i]*3;
        if(n%3==1) wb[ta++]=n-1;
        sort(r,wb,wa,ta,m);
        for(i=0;i<tbc;i++) wv[wb[i]=G(san[i])]=i;
        for(i=0,j=0,p=0;i<ta && j<tbc;p++)
        sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
        for(;i<ta;p++) sa[p]=wa[i++];
        for(;j<tbc;p++) sa[p]=wb[j++];
        return;
    }
int height[N], rank[N];
void calheight(int *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[j+k]==r[i+k];k++);
}

int sa[N],r[N], n, dp[N];
char str[N];

void InitRMQ(){
    int x = rank[0]; dp[x] = n;
    for(int i = x-1; i >= 1; i--)
        dp[i] = min( dp[i+1], height[i+1] );
    for(int i = x+1; i <= n; i++)
        dp[i] = min( dp[i-1], height[i] );
}
vector<int> S;
int main(){
    while( scanf("%s", str) != EOF )
    {
        if( strcmp(str,".") == 0 ) break;
        n = strlen(str);
        for(int i = 0; i < n; i++)
            r[i] = str[i];
        r[n] = 0;

        dc3(r,sa,n+1,128);
        calheight(r,sa,n);

        InitRMQ();
        S.clear();
        for(int i = 1; i*i <= n; i++)
        {
            if(n%i==0){
                S.push_back(i);
                if( i*i != n ) S.push_back(n/i);
            }
        }
        sort(S.begin(),S.end());
        for(vector<int>::iterator it = S.begin(); it != S.end(); it++ ){
            int L = *it;
            if( n%L == 0 ){
                int t = dp[rank[L]]; //query( rank[0], rank[L] );
                if( t == n-L ){
                     printf("%d\n", n/L );
                     break;
                }
            }
        }
    }
    return 0;
}
DC3

 

      poj 3693 連續重復次數最多的子串.

      枚舉構成 子串的單元長度L,  那么將原串用長度L等分為(0,L,2L,...最后段可能不全).

        重復一次的我們特殊處理,顯然是rank[1]所對應的單個字符。

      我們只考慮 “單元串” 重復兩次或以上構成的子串,那么我們可以知道,若存在長度為L的“單元串”,則必定有一對相鄰的點 { L*i,L*(i+1) } 分別屬於兩個不同的“單元串”, 當以這兩個為起點的后綴的LCP為M,則其將已經匹配長度為L的“單元串“ M/L+1次。  這里有個問題是,我們求得的 單元串重復多次構成不一定是最優或者起點,因為 M%L != 0的時候,可能不是起點, 這個時候  起點可能是  L*i - (L-M%L) , 抽象理解是補全循環,使其成為L的倍數,讓它重復次數最大。

      雖然這樣,我們可以求出 最大重復次數 與其對應的長度。 但是題目需要求最小的字典序。 我們可以將最大重復次數與 合法的長度都存儲起來(可能有多個合法長度,但重復次數唯一),然后我們可以 按排名從1-N分別求出對應長度的 最小字典序,然后取一個最小的排名即可。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
const int N = int(1e5)+10;
int cmp(int *r,int a,int b,int l){
    return (r[a]==r[b]) && (r[a+l]==r[b+l]);
}
// 用於比較第一關鍵字與第二關鍵字,
// 比較特殊的地方是,預處理的時候,r[n]=0(小於前面出現過的字符)

int wa[N],wb[N],ws[N],wv[N];
int rank[N],height[N];
void da(int *r,int *sa,int n,int m){ //此處N比輸入的N要多1,為人工添加的一個字符,用於避免CMP時越界
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i; //預處理長度為1
    for(j=1,p=1;p<n;j*=2,m=p) //通過已經求出的長度J的SA,來求2*J的SA
    {
        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; //利用長度J的,按第二關鍵字排序
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<m;i++) ws[i]=0;
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[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++;  //更新名次數組x[],注意判定相同的
    }
}

void calheight(int *r,int *sa,int n){ // 此處N為實際長度
    int i,j,k=0;        // height[]的合法范圍為 1-N, 其中0是結尾加入的字符
    for(i=1;i<=n;i++) rank[sa[i]]=i;  // 根據SA求RANK
    for(i=0;i<n; height[rank[i++]] = k ) // 定義:h[i] = height[ rank[i] ]
    for(k?k--:0,j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++); //根據 h[i] >= h[i-1]-1 來優化計算height過程
}
int sa[N],r[N], n;
char str[N];
int dp[N][20];

void InitRMQ(){
    for(int i = 1; i <= n; i++)
        dp[i][0] = height[i];
    for(int j = 1; (1<<j)<=n; j++)
        for(int i = 1; i+(1<<j)-1<=n; i++)
            dp[i][j] = min( dp[i][j-1], dp[i+(1<<(j-1))][j-1] );
}
int query(int L,int R){
    if(L>R) swap(L,R); L++;
    int k = (int)floor( log(1.*(R-L+1))/log(2.0) );
    return min( dp[L][k], dp[R-(1<<k)+1][k] );
}
int main(){
    int Case = 1;
    while( scanf("%s",str) != EOF)
    {
        if(strcmp(str,"#")==0) break;
         printf("Case %d: ", Case++ );

        n = strlen(str);
        for(int i = 0; i < n; i++)
            r[i] = str[i]-'a'+1;
        r[n] = 0;

        da(r,sa,n+1,27);
        calheight(r,sa,n);
        InitRMQ();
        int maxk = 0, maxl = 0;
        vector<int> S; S.clear();
        for(int L = 1; L < n; L++)
        {
//            printf("L = %d\n", L);
            for(int i = 0; i+L < n; i+=L )
            {
                int M = query( rank[i], rank[i+L] );
                int k = M/L+1, left = i;
                int t = L-M%L;
                t = i-t;
              //  printf("t = %d, M = %d, i = %d\n", t, M, i );
                if( (t>=0) && (M%L) )
                {
                    int M1 = query( rank[t], rank[t+L] );
                    int k1 = M1/L+1;
                    if( k1 > k ) k = k1;
                }
//                printf("i = %d, k = %d, L = %d\n", i, k, L);
                if( k > maxk ){
                    maxk = k; maxl = L;
                    S.clear(); S.push_back(L);
                }
                else if( (k==maxk) && (maxl<L) )
                    S.push_back(L), maxl = L;
            }
        }
        // debug
//        for(int i = 0; i < S.size(); i++)
//            printf("L = %d\n", S[i] );

        if( (S.size()==0) || (maxk==1) ){
            printf("%c\n", str[sa[1]] );
            continue;
        }
        int st, len = -1;
        vector< pair<int,int> >res; res.clear();

//        printf("maxk = %d\n", maxk );
        for(int j = 0; j < (int)S.size(); j++)
        {
            int L = S[j];
//            printf("===> L = %d\n", L);
            for(int i = 1; i <= n; i++)
            {
                if( sa[i]+L < n ){
                    int M = query( i, rank[sa[i]+L] );
//                    printf("i=%d,[%d,%d], M = %d\n", i,sa[i],sa[i]+L, M );
                    if( M >= L*(maxk-1) ){
                        res.push_back( make_pair(sa[i],L) );
                        break;
                    }
                }
            }
        }
//        printf("Debug:\n");
//        printf("LCP(3,5) = %d\n", query( rank[3], rank[5] ));
//         for(vector< pair<int,int> >::iterator it=res.begin(); it != res.end(); it++ )
//         {
//             printf("st = %d, L = %d, rank = %d\n", it->first, it->second, rank[it->first]);
//         }
        int Min = n+1;
        for(vector< pair<int,int> >::iterator it=res.begin(); it != res.end(); it++ )
        {
            int a = it->first, b = it->second;
            if( rank[a] < Min ) st = a, len = b, Min = rank[a];
        }
        len *= maxk;
        //printf("st = %d\n", st );
        for(int i = 0; i < len; i++)
            printf("%c", str[st+i] );
        puts("");
    }
    return 0;
}
View Code

 

    spoj 687 repeats  做法同上題樣,只要求最大次數,代碼簡單了多。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
const int N = int(1e5)+10;
int cmp(int *r,int a,int b,int l){
    return (r[a]==r[b]) && (r[a+l]==r[b+l]);
}
// 用於比較第一關鍵字與第二關鍵字,
// 比較特殊的地方是,預處理的時候,r[n]=0(小於前面出現過的字符)

int wa[N],wb[N],ws[N],wv[N];
int rank[N],height[N];
void da(int *r,int *sa,int n,int m){ //此處N比輸入的N要多1,為人工添加的一個字符,用於避免CMP時越界
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i; //預處理長度為1
    for(j=1,p=1;p<n;j*=2,m=p) //通過已經求出的長度J的SA,來求2*J的SA
    {
        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; //利用長度J的,按第二關鍵字排序
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<m;i++) ws[i]=0;
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[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++;  //更新名次數組x[],注意判定相同的
    }
}

void calheight(int *r,int *sa,int n){ // 此處N為實際長度
    int i,j,k=0;        // height[]的合法范圍為 1-N, 其中0是結尾加入的字符
    for(i=1;i<=n;i++) rank[sa[i]]=i;  // 根據SA求RANK
    for(i=0;i<n; height[rank[i++]] = k ) // 定義:h[i] = height[ rank[i] ]
    for(k?k--:0,j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++); //根據 h[i] >= h[i-1]-1 來優化計算height過程
}
int sa[N],r[N], n;
char str[N];
int dp[N][20];

void InitRMQ(){
    for(int i = 1; i <= n; i++)
        dp[i][0] = height[i];
    for(int j = 1; (1<<j)<=n; j++)
        for(int i = 1; i+(1<<j)-1<=n; i++)
            dp[i][j] = min( dp[i][j-1], dp[i+(1<<(j-1))][j-1] );
}
int query(int L,int R){
    if(L>R) swap(L,R); L++;
    int k = (int)floor( log(1.*(R-L+1))/log(2.0) );
    return min( dp[L][k], dp[R-(1<<k)+1][k] );
}
int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d", &n);
        for(int i = 0; i < n; i++){
            scanf("%s", str );
            r[i] = str[0]-'a'+1;
        }
        r[n] = 0;

        da(r,sa,n+1,27);
        calheight(r,sa,n);
        InitRMQ();
        int maxk = 0, maxl = 0;
        for(int L = 1; L < n; L++)
        {
//            printf("L = %d\n", L);
            for(int i = 0; i+L < n; i+=L )
            {
                int M = query( rank[i], rank[i+L] );
                int k = M/L+1, left = i;
                int t = L-M%L;
                t = i-t;
              //  printf("t = %d, M = %d, i = %d\n", t, M, i );
                if( (t>=0) && (M%L) )
                {
                    int M1 = query( rank[t], rank[t+L] );
                    int k1 = M1/L+1;
                    if( k1 > k ) k = k1;
                }
//                printf("i = %d, k = %d, L = %d\n", i, k, L);
                if( k > maxk ) maxk = k;
            }
        }
        printf("%d\n", maxk);
    }
    return 0;
}
View Code

 

  兩個字符串相關問題

     兩個串的最長公共子串長度

      poj 3693  將兩串拼接起來,中間連接出插入特殊字符, 然后問題就轉換成了求最長的LCP,其滿足兩個后綴分屬不同的串, 簡單推理下,可以知道,根據傳遞性,兩串的最長LCP必定相鄰, 可以用反證法證明.

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int N = int(2e5)+10;

int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int wa[N],wb[N],ws[N],wv[N];
int height[N], rank[N];

void da(int *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[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<m;i++) ws[i]=0;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
        for(swap(x,y),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(int *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[j+k]==r[i+k];k++);
}


char s1[N], s2[N];
int r[N], n1, n2, n;
int sa[N];

int main(){
    while( scanf("%s%s",s1,s2) != EOF)
    {
        n1 = strlen(s1); n2 = strlen(s2);
        // {0,n1-1}, n1+{ 1,n2 }
        for(int i = 0; i < n1; i++)
            r[i] = (int)s1[i];
        r[n1] = 1;
        for(int i = 0; i < n2; i++)
            r[ n1+1+i ] = (int)s2[i];
        r[n1+n2+1] = 0;
        n = n1+n2;

        da(r,sa,n+1,128);
        calheight(r,sa,n);
        int Max = 0, st;
        for(int i = 2; i <= n; i++)
        {
            if( height[i] > Max ){
                if( (sa[i-1]<n1&&sa[i]>n1) || (sa[i-1]>n1&&sa[i]<n1) )
                    Max = height[i], st = sa[i]>n1?sa[i-1]:sa[i];
            }
        }
        printf("%d\n", Max);
       /* for(int i = 0; i < Max; i++)
            printf("%c", s1[st+i]);
        puts("");*/
    }
    return 0;
}
View Code

 

  

      spoj 687 同上題一樣,不過注意 N需要開到 3e5.否則會RE

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int N = int(3e5)+100;

int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int wa[N],wb[N],ws[N],wv[N];
int height[N], rank[N];

void da(int *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[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<m;i++) ws[i]=0;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
        for(swap(x,y),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(int *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[j+k]==r[i+k];k++);
}


char s1[N], s2[N];
int r[N], n1, n2, n;
int sa[N];

int main(){
    while( scanf("%d", &n) != EOF)
   // while( scanf("%s%s",s1,s2) != EOF)
    {
        scanf("%s",s1); scanf("%s",s2);
        n1 = strlen(s1); n2 = strlen(s2);
        // {0,n1-1}, n1+{ 1,n2 }
        for(int i = 0; i < n1; i++)
            r[i] = (int)s1[i];
        r[n1] = 1;
        for(int i = 0; i < n2; i++)
            r[ n1+1+i ] = (int)s2[i];
        r[n1+n2+1] = 0;
        n = n1+n2;

        da(r,sa,n+1,128);
        calheight(r,sa,n);
        int Max = 0, st;
        for(int i = 2; i <= n; i++)
        {
            if( height[i] > Max ){
                if( (sa[i-1]<n1&&sa[i]>n1) || (sa[i-1]>n1&&sa[i]<n1) )
                    Max = height[i], st = (sa[i]<n1?sa[i]:sa[i-1]);
            }
        }
      //  printf("%d\n", Max);
        for(int i = 0; i < Max; i++)
            printf("%c", s1[st+i]);
        puts("");
    }
    return 0;
}
View Code

 

 

     兩個串的公共子串個數

      poj 3415 求 長度不小於K的公共子串個數(可相同)。題目比較經典~~

      分析過程:首先主觀想法是,串A長度為n1,串B長度為n2, 總數量就是兩個串的笛卡爾積。串A的n1個后綴與串B的n2個后綴滿足要求的匹配數量。

      對於10^5的N,O(N^2)顯然不行,我們可以嘗試着轉換,是否計算過程能否轉換成O(N)或者O(nlogn)的。

      再仔細分析下,計算的過程,枚舉串A所有后綴 st, 與B的所有后綴匹配。 我們提出一個猜想,當我們枚舉串A的一個后綴suffix(i)與串B所有后綴計算完成后,再枚舉 串A的后綴suffix(i+1) ,能否利用suffix(i)的計算信息來降低 掃描串B的所有后綴的 時間花費呢。 顯然suffix(i) 與 suffix(i+1) 之間的關系對於公共子串而言 並不關鍵。但是LCP(最長公共前綴)就比較有用了。

         關於排序后的后綴的性質這里就不說了。我們直接切入主題,相鄰后綴不行,我們嘗試使用相鄰排名來處理。若我們將兩個串連接起來,(連接點插入一個特殊字符)。

       枚舉串A的所有排名等價枚舉所有A的后綴.當我們按照排名從小到大枚舉 后綴x(可能是A的,也有可能是B的), 若假定此時是A的一個后綴, 那其前面的(排名比起小的)后綴中B的一個后綴y, 若與A的后綴x之間的 LCP( x,y ) >= k,  則此時有 ,若 y < x , 公共子串數量為y-k+1,  若 y > x, 公共子串數量為x-k+1,  顯然與期間的最小值有關。

      顯然,若這樣計算,我們只計算了 A的所有后綴x 與 排名比其小的B的后綴, 那些比X大的B的后綴還未被計算到。 下意識會想到倒着做一次,就可以了。 其實我們可以對B同A那樣求一次,就等價反向再對A求一次了。

      大概計算的思路就在這里了, 不過我們還需要解決如何利用前面的值來簡化求解時間。

      為了便於分析,我們假定只考慮 計算后綴排名在A之前的這一部分,因為另一部分是類似的。

      首先我們將 height[i] = max( 0, height[i]-K+1 ),表示當前后綴貢獻的子串數量。

      維護一個 嚴格單調遞增的 棧,在處理過程中,我們需要記錄一個當前合法的串B的后綴貢獻的子串數量,當加入進來一個A串時候,所有比起小的串B后綴都應該被納入計算,比起大的部分我們應該減掉,  此時就需要記錄那部分的數量。 中間過程不太好用文字描述。。。具體看代碼吧。

       

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = int(2e5)+10;
typedef long long LL;

int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int wa[N],wb[N],ws[N],wv[N];
int height[N], rank[N];

void da(int *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[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<m;i++) ws[i]=0;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
        for(swap(x,y),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(int *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[j+k]==r[i+k];k++);
}

int n, n1, n2, K;
int sa[N], r[N];
char s1[N], s2[N];

int h[N],na[N],nb[N],S[N];

void solve(){
    for(int i = 2; i <= n; i++)
        h[i] = max(0,height[i]-K+1);
    LL ans = 0, w1 = 0, w2 = 0;
    int top = 0;
    for(int i = 2; i <= n; i++)
    {
        S[++top] = h[i];
        if( sa[i-1] <= n1 ) na[top]=1,nb[top]=0,w1 += h[i];
        else na[top]=0, nb[top]=1, w2 += h[i];
        while( (top>1) && (S[top]<=S[top-1]) ){
            w1 -= na[top-1]*(S[top-1]-S[top]);
            w2 -= nb[top-1]*(S[top-1]-S[top]);
            na[top-1] += na[top]; nb[top-1] += nb[top];
            S[top-1] = S[top];
            top--;
        }
        if( sa[i] <= n1 ) ans += w2;
        else ans += w1;
    }
    printf("%lld\n", ans);
}
int main(){
    while( scanf("%d", &K), K)
    {
        scanf("%s%s",s1,s2);
        n1 = strlen(s1); n2 = strlen(s2);
        for(int i =  0; i < n1; i++)
            r[i] = (int)s1[i];
        r[n1] = 1;
        for(int i = 0; i < n2; i++)
            r[n1+1+i] = (int)s2[i];
        r[n=n1+n2+1] = 0;

        da(r,sa,n+1,128);
        calheight(r,sa,n);
        solve();
    }
    return 0;
}
View Code

 

  多個字符串

      通常解法是將其連接成一個串,然后跑DC3 or DA。 

      poj 3294  解法是二分枚舉長度公共串長度L,將height值按>=L分組。然后隨便搞就好了。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N = int(3e5)+10;
int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int wa[N],wb[N],ws[N],wv[N];
int height[N], rank[N];

void da(int *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[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<m;i++) ws[i]=0;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
        for(swap(x,y),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(int *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[j+k]==r[i+k];k++);
}

char str[110][1010];
int r[N], sa[N], n, len[110], a[110], cnt;
bool vis[N];
int K;

vector<int> S[N];
bool check(int L){
    int cur = -1;
    for(int i = 1; i <= n; i++){
        if( height[i] < L ) S[++cur].clear();
        S[cur].push_back(i);
    }
    for(int i = 0; i <= cur; i++){
        if( S[i].size() > K ){
            memset(vis,0,sizeof(vis));
            for(int j = 0; j < S[i].size(); j++)
            {
                int k = S[i][j];
                int x = upper_bound(a,a+cnt+1, sa[ S[i][j] ] )-a-1;
                vis[x] = true;
            }
            int count = 0;
            for(int j = 0; j < cnt; j++)
                if(vis[j]) count++;
            if( count > K ) return true;
        }
    }
    return false;
}
void gao(int L){
    int cur = -1;
    for(int i = 1; i <= n; i++){
        if( height[i] < L ) S[++cur].clear();
        S[cur].push_back(i);
    }
    for(int i = 0; i <= cur; i++){
        if( S[i].size() > K ){
            memset(vis,0,sizeof(vis));
            for(int j = 0; j < S[i].size(); j++)
            {
                int k = S[i][j];
                int x = upper_bound(a,a+cnt+1, sa[ S[i][j] ] )-a-1;
                vis[x] = true;
            }
            int count = 0;
            for(int j = 0; j < cnt; j++)
                if(vis[j]) count++;
            if( count > K )
            {
                for(int j = 0; j < L; j++ )
                    printf("%c", char(r[ sa[S[i][0]]+j ]) );
                puts("");
            }
        }
    }
}
void solve(){
    da(r,sa,n+1,310);
    calheight(r,sa,n);
    K = cnt/2;

    int l = 1, r = 1000, L = 0;
    while( l <= r ){
        int m = (l+r)>>1;
        if( check(m) ) L=m,l=m+1;
        else r = m-1;
    }
    if( L == 0 ) puts("?");
    else gao(L);
}
int main(){
    int Case = 0;
    while( scanf("%d",&cnt), cnt )
    {
        if( Case++ > 0 ) puts("");
        for(int i = 0; i < cnt; i++)
        {
            scanf("%s",str[i]);
            len[i] = strlen(str[i]);
        }
        n = 0;
        int tmp = 200;
        for(int i = 0; i < cnt; i++)
        {
            if(i>0) r[n++] = tmp++;
            for(int j = 0; j < len[i]; j++)
                r[n++] = (int)str[i][j];
        }
        r[n] = 0;

        tmp = 0;
        for(int i = 0; i <= cnt; i++)
        {
            a[i] = tmp;
            if( i < cnt ) tmp = tmp + (i==0?len[i]:len[i]+1);
        }
        solve();
    }
    return 0;
}
View Code

 

      spoj 220 題目給出10個長度為10^4的串,讓求在每個串中都至少出現兩次的不重疊 子串最大長度。

      做法同樣是合並成一個串,然后二分子串長度L,因為不同串數量才10,暴力做判定就好了。關於不重疊和前面一樣同一組Max-Min>=L即可。不過這里要注意是對於每一種串都必須滿足才可。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
using namespace std;
const int N = int(3e5)+10;

int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}
int wa[N],wb[N],ws[N],wv[N];
int height[N], rank[N];

void da(int *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb;
    for(i=0;i<m;i++) ws[i]=0;
    for(i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[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<m;i++) ws[i]=0;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<n;i++) ws[wv[i]]++;
        for(i=1;i<m;i++) ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
        for(swap(x,y),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(int *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[j+k]==r[i+k];k++);
}



char str[15][10100];
int len[15], a[N], n, M;
int sa[N], r[N];
vector<int> S[N];
int Min[15], Max[15];

bool check(int L){
    int cur = -1;
    for(int i = 1; i <= n; i++){
        if( height[i] < L ) S[++cur].clear();
        S[cur].push_back(i);
    }
    for(int i = 0; i <= cur; i++)
    {
        if( S[i].size() >= 2*M ){
            memset(Min,-1,sizeof(Min));
            memset(Max,-1,sizeof(Max));

            for(int j = 0; j < S[i].size(); j++)
            {
                int v = S[i][j]; // rank
                int idx = upper_bound(a,a+M+1,sa[v])-a-1;
                Min[idx] = Min[idx]==-1? sa[v]: min(Min[idx],sa[v]);
                Max[idx] = Max[idx]==-1? sa[v]: max(Max[idx],sa[v]);
            }
            bool flag = true;
            for(int i = 0; i < M; i++){
                if( (Min[i]==-1) || (Max[i]-Min[i]<L) )
                {
                    flag = false; break;
                }
            }
            if(flag) return true;
        }
    }
    return false;
}
void solve(){
    da(r,sa,n+1,128);
    calheight(r,sa,n);
    int l = 1, r = N, ans = 0;
    while( l <= r ){
        int m = (l+r)>>1; //printf("m = %d\n", m);
        if( check(m) ) ans=m, l = m+1;
        else r = m-1;
    }
    printf("%d\n", ans );
}
int main(){
    int _;
    scanf("%d", &_);
    while( _-- )
    {
        scanf("%d", &M);
        for(int i = 0; i < M; i++)
        {
            scanf("%s", str[i]);
            len[i] = strlen(str[i]);
        }
        n = 0; int tmp = 0;
        for(int i = 0; i < M; i++)
        {
            if(i > 0) r[n++] = ++tmp;
            for(int j = 0; j < len[i]; j++)
                r[n++] = int(str[i][j]);
        }
        r[n] = 0; tmp = 0;
        for(int i = 0; i <= M; i++){
            a[i] = tmp;
            if(i < M) tmp += (i==0)?len[i]:len[i]+1;
        }
        solve();
    }
    return 0;
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM