後綴數組總結

後綴數組求不可重疊最長重複子串

題意:有N(1 <= N <=20000)個音符的序列來表示一首樂曲,每個音符都是1..88範圍內的整數,現在要找一個重複的主題。“主題”是整個音符序列的一個子串,它需要滿足如下條件:

    1.長度至少爲5個音符。

    2.在樂曲中重複出現。(可能經過轉調,“轉調”的意思是主題序列中每個音符都被加上或減去了同一個整數值)

    3.重複出現的同一主題不能有公共部分。

這是論文裏一道題,那麼我們通過進行二分來求論文裏的k值,求出height[i]大於k的時候,然後求出這個區間裏面起始位置最大的差值,要求差值也要大於k,這樣才保證沒有重疊,這是因爲height裏的都是已經按照字典序排好的了

Sample Input

30
25 27 30 34 39 45 52 60 69 79 69 60 52 45 39 34 30 26 22 18
82 78 74 70 66 67 64 60 65 80
0

Sample Output

5
#include <stdio.h>
#include <algorithm>
#include <iostream>
using namespace std;
#define maxn 22222
int n;
int s[maxn],c[maxn],sa[maxn],x[maxn],y[maxn],height[maxn],rrank[maxn];
void SA(int m)
{
    for(int i=0;i<m;i++)
        c[i]=0;
    for(int i=0;i<n;i++)
        c[x[i]=s[i]]++;
    for(int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int p=0;
        for(int i=n-k;i<n;i++)
            y[p++]=i;
        for(int i=0;i<n;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
//        for(int i=0;i<n;i++)
//            cout << y[i] << " " ;
//        cout << endl;
        for(int i=0;i<m;i++)
            c[i]=0;
        for(int i=0;i<n;i++)
            c[x[y[i]]]++;
        for(int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
//        for(int i=0;i<n;i++)
//            cout << sa[i] << " " ;
//        cout << endl;
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
//        for(int i=0;i<n;i++)
//            cout <<x[i] <<" " ;
//        cout << endl;
        if(p>=n)
            break;
        m=p;
    }
}
void getheight()
{
    int k=0;
    for(int i=0;i<=n;i++)
        rrank[sa[i]]=i;
    for(int i=0;i<n;i++)
    {
        if(k)
            k--;
        int j=sa[rrank[i]-1];
        while(s[i+k]==s[j+k])
            k++;
        height[rrank[i]]=k;
    }
//    for(int i=0;i<n;i++)
//        cout << height[i] <<" ";
//    cout <<endl;
}
int check(int len)
{
    int maxx=0,minn=maxn;
    for(int i=2;i<=n;i++)
    {
        if(height[i]>=len)
        {
            maxx=max(maxx,sa[i]);
            minn=min(minn,sa[i]);
            if(maxx-minn>len)
                return 1;
        }
        else
        {
            maxx=sa[i];
            minn=sa[i];
        }
    }
    return 0;
}
int main()
{
    while(scanf("%d",&n)!=EOF&&n)
    {
        int ans=0;
        int x;
        scanf("%d",&x);
        for(int i=1;i<n;i++)
        {
            int mid;
            scanf("%d",&mid);
            s[i-1]=mid-x+100;
            x=mid;
        }
        s[n-1]=0;
        SA(200);
        n--;
        getheight();
        int l=0;
        int r=n;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(check(mid))
            {
                ans=mid;
                l=mid+1;
            }
            else
                r=mid-1;
        }
        if(ans<4)
            ans=-1;
        printf("%d\n",ans+1);
    }
}

可重疊的k次最長重複子串 

題意:找出出現k次的可重疊的最長子串的長度

通過二分長度,然後分成若干組去尋找答案

Sample Input

8 2
1
2
3
2
3
2
3
1

Sample Output

4
#include <stdio.h>
#include <algorithm>
using namespace std;
#define maxn 22222
int n;
int s[maxn],c[maxn],sa[maxn],x[maxn],y[maxn],height[maxn],rrank[maxn];
void SA(int m)
{
    for(int i=0;i<m;i++)
        c[i]=0;
    for(int i=0;i<n;i++)
        c[x[i]=s[i]]++;
    for(int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int p=0;
        for(int i=n-k;i<n;i++)
            y[p++]=i;
        for(int i=0;i<n;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
//        for(int i=0;i<n;i++)
//            cout << y[i] << " " ;
//        cout << endl;
        for(int i=0;i<m;i++)
            c[i]=0;
        for(int i=0;i<n;i++)
            c[x[y[i]]]++;
        for(int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
//        for(int i=0;i<n;i++)
//            cout << sa[i] << " " ;
//        cout << endl;
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
//        for(int i=0;i<n;i++)
//            cout <<x[i] <<" " ;
//        cout << endl;
        if(p>=n)
            break;
        m=p;
    }
}
void getheight()
{
    int k=0;
    for(int i=0;i<=n;i++)
        rrank[sa[i]]=i;
    for(int i=0;i<n;i++)
    {
        if(k)
            k--;
        int j=sa[rrank[i]-1];
        while(s[i+k]==s[j+k])
            k++;
        height[rrank[i]]=k;
    }
//    for(int i=0;i<n;i++)
//        cout << height[i] <<" ";
//    cout <<endl;
}
int check(int len,int k)
{
    int cnt=1;
    for(int i=2;i<=n;i++)
    {
        if(height[i]>=len)
        {
            cnt++;
            if(cnt>=k)
                return 1;
        }
        else
            cnt=1;
    }
    return 0;
}
int main()
{
    int k;
    int maxx=0;
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&s[i]);
        maxx=max(maxx,s[i]);
    }
    s[n]=0;
    n++;
    SA(maxx+1);
    n--;
    getheight();
    int l=0;
    int r=n;
    int ans=0;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(check(mid,k))
        {
            ans=mid;
            l=mid+1;
        }
        else
            r=mid-1;
    }
    printf("%d\n",ans);
}

求不相同的子串個數

題意:給定一個字符串,求不相同的子串。
思路:每個子串一定是某個後綴的前綴,那麼原問題等價於求所有後綴之間的不相同的前綴的個數。如果所有的後綴按照 suffix(sa[1]), suffix(sa[2]),
suffix(sa[3]), …… ,suffix(sa[n])的順序計算,不難發現,對於每一次新加
進來的後綴 suffix(sa[k]),它將產生 n-sa[k]個新的前綴。但是其中有height[k]個是和前面的字符串的前綴是相同的。所以 suffix(sa[k])將“貢獻”
出 n-sa[k]-height[k]個不同的子串。累加後便是原問題的答案。

Sample Input:
2
CCCCC
ABABA

Sample Output:
5
9

#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
using namespace std;
#define maxn 55555
long long int  n;
char s[maxn];
long long int  c[maxn],sa[maxn],x[maxn],y[maxn],height[maxn],rrank[maxn];
void SA(long long int  m)
{
    for(long long int  i=0;i<m;i++)
        c[i]=0;
    for(long long int  i=0;i<n;i++)
        c[x[i]=s[i]]++;
    for(long long int  i=1;i<m;i++)
        c[i]+=c[i-1];
    for(long long int  i=n-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(long long int  k=1;k<=n;k<<=1)
    {
        long long int  p=0;
        for(long long int  i=n-k;i<n;i++)
            y[p++]=i;
        for(long long int  i=0;i<n;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
//        for(long long int  i=0;i<n;i++)
//            cout << y[i] << " " ;
//        cout << endl;
        for(long long int  i=0;i<m;i++)
            c[i]=0;
        for(long long int  i=0;i<n;i++)
            c[x[y[i]]]++;
        for(long long int  i=1;i<m;i++)
            c[i]+=c[i-1];
        for(long long int  i=n-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
//        for(long long int  i=0;i<n;i++)
//            cout << sa[i] << " " ;
//        cout << endl;
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(long long int  i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
//        for(long long int  i=0;i<n;i++)
//            cout <<x[i] <<" " ;
//        cout << endl;
        if(p>=n)
            break;
        m=p;
    }
}
void getheight()
{
    long long int  k=0;
    for(long long int  i=0;i<=n;i++)
    {
        rrank[sa[i]]=i;
        //cout << sa[i] << endl;
    }
    for(long long int  i=0;i<n;i++)
    {
        if(k)
            k--;
        long long int  j=sa[rrank[i]-1];
        while(s[i+k]==s[j+k])
            k++;
        //cout << rrank[i] << " " << k <<endl;
        height[rrank[i]]=k;
    }
//    for(long long int  i=0;i<n;i++)
//        cout << height[i] <<" ";
//    cout <<endl;
}
int  main()
{
    long long int  t;
    scanf("%lld",&t);
    while(t--)
    {
        scanf("%s",s);
        n=strlen(s);
        n++;
        SA(300);
        n--;
        getheight();
        long long int  ans=n*(n+1)/2;
        for(long long int  i=2;i<=n;i++)
        {
            //cout << height[i] <<endl;
            ans-=height[i];
        }
        printf("%lld\n",ans);
    }
}

重複次數最多的連續重複子串

Sample Input

1
17
b
a
b
b
a
b
a
a
b
a
a
b
a
a
b
a
b

 Sample Output

4

【題意】

給定一個字符串,求重複次數最多的連續重複子串

【類型】
後綴數組[重複次數最多的連續重複子串]
【分析】

本題是一道裸的後綴數組題

"重複次數最多的連續重複子串"解法(摘自羅穗騫的國家集訓隊論文):

先窮舉長度L,然後求長度爲L的子串最多能連續出現幾次。首先連續出現1次是肯定可以的,所以這裏只考慮至少2次的情況。假設在原字符串中連續出現2次,記這個子字符串爲S,那麼S肯定包括了字符r[0], r[L], r[L*2],r[L*3], ……中的某相鄰的兩個。所以只須看字符r[L*i]和r[L*(i+1)]往前和
往後各能匹配到多遠,記這個總長度爲K,那麼這裏連續出現了K/L+1次。最後看最大值是多少。如圖所示。

 

窮舉長度L的時間是n,每次計算的時間是n/L。所以整個做法的時間複雜度是O(n/1+n/2+n/3+……+n/n)=O(nlogn)。

 

ps:基本思路在羅穗騫的論文裏已經說得比較清楚了,而我在這裏要提的是論文裏比較模糊的部分

要提一提的總共有兩點,第一點比較顯而易見

“S肯定包括了字符r[0], r[L], r[L*2],r[L*3], ……中的某相鄰的兩個”

由於當前S是有兩個長度爲L的連續重複子串拼接而成的,那意味着S[i]和S[i+L](0≤i<L)必定是一樣的字符

而這兩個字符位置相差L

而字符r[0],r[L],r[L*2],r[L*3],......中相鄰兩個的位置差均爲L

“只須看字符r[L*i]和r[L*(i+1)]往前和往後各能匹配到多遠”,對於往後能匹配到多遠,這個直接根據最長公共前綴就能很容易得到,即上圖中的後綴Suffix(6)和後綴Suffix(9)的最長公共前綴。而對於往前能匹配到多遠,我們當然可以一開始就把字符串反過來拼在後面,這樣也能根據最長公共前綴來看往前能匹配到多遠,但這樣效率就比較低了。

其實,當枚舉的重複子串長度爲i時,我們在枚舉r[i*j]和r[i*(j+1)]的過程中,必然可以出現r[i*j]在第一個重複子串裏,而r[i*(j+1)]在第二個重複子串裏的這種情況,如果此時r[i*j]是第一個重複子串的首字符,這樣直接用公共前綴k除以i並向下取整就可以得到最後結果。但如果r[i*j]如果不是首字符,這樣算完之後結果就有可能偏小,因爲r[i*j]前面可能還有少許字符也能看作是第一個重複子串裏的。
於是,我們不妨先算一下,從r[i*j]開始,除匹配了k/i個重複子串,還剩餘了幾個字符,剩餘的自然是k%i個字符。如果說r[i*j]的前面還有i-k%i個字符完成匹配的話,這樣就相當於利用多餘的字符還可以再匹配出一個重複子串,於是我們只要檢查一下從r[i*j-(i-k%i)]和r[i*(j+1)-(i-k%i)]開始是否有i-k%i個字符能夠完成匹配即可,也就是說去檢查這兩個後綴的最長公共前綴是否比i-k%i大即可。
當然如果公共前綴不比i-k%i小,自然就不比i小,因爲後面的字符都是已經匹配上的,所以爲了方便編寫,程序裏面就直接去看是否會比i小就可以了。

#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
#define maxn 55555
int n;
char s[maxn];
int c[maxn],sa[maxn],x[maxn],y[maxn],height[maxn],rrank[maxn],st[maxn][20];
void SA(int m)
{
    for(int i=0;i<m;i++)
        c[i]=0;
    for(int i=0;i<n;i++)
        c[x[i]=s[i]]++;
    for(int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int p=0;
        for(int i=n-k;i<n;i++)
            y[p++]=i;
        for(int i=0;i<n;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
        for(int i=0;i<m;i++)
            c[i]=0;
        for(int i=0;i<n;i++)
            c[x[y[i]]]++;
        for(int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        if(p>=n)
            break;
        m=p;
    }
}
void getheight()
{
    int k=0;
    for(int i=0;i<=n;i++)
        rrank[sa[i]]=i;
    for(int i=0;i<n;i++)
    {
        if(k)
            k--;
        int j=sa[rrank[i]-1];
        while(s[i+k]==s[j+k])
            k++;
        height[rrank[i]]=k;
    }
}
void ST()
{
    for(int i=1;i<=n;i++)
        st[i][0]=height[i];
    for(int j=1;j<=19;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
int getmin(int x,int y)
{
    int l=rrank[x];
    int r=rrank[y];
    if(l>r)
        swap(l,r);
    l++;
    int k=log2(r-l+1);
    return min(st[l][k],st[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(" %c",&s[i]);
        s[n]=0;
        n++;
        SA(128);
        n--;
        getheight();
        ST();
        //cout << getmin(0,2) << endl;
        int ans=0;
        for(int i=1;i<=n;i++)
            for(int j=0;j+i<n;j+=i)
        {
            int tmp=getmin(j,j+i);
            int k=j-(i-tmp%i);
            tmp/=i;
            if(k>=0&&getmin(k,k+i)>=i)
                tmp++;
            ans=max(ans,tmp+1);
        }
        printf("%d\n",ans);
    }

}

 

重複次數最多的連續重複子串

題意:給定一個串,長度<=10^5,求它重複次數最多的連續重複子串(輸出字典序最小的那個)。

例如ccabcabc,答案就是abcabc

#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
#define maxn 111111
int n;
char s[maxn];
int c[maxn],sa[maxn],x[maxn],y[maxn],height[maxn],rrank[maxn],st[maxn][20];
void SA(int m)
{
    for(int i=0;i<m;i++)
        c[i]=0;
    for(int i=0;i<n;i++)
        c[x[i]=s[i]]++;
    for(int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int p=0;
        for(int i=n-k;i<n;i++)
            y[p++]=i;
        for(int i=0;i<n;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
        for(int i=0;i<m;i++)
            c[i]=0;
        for(int i=0;i<n;i++)
            c[x[y[i]]]++;
        for(int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        if(p>=n)
            break;
        m=p;
    }
}
void getheight()
{
    int k=0;
    for(int i=0;i<=n;i++)
        rrank[sa[i]]=i;
    for(int i=0;i<n;i++)
    {
        if(k)
            k--;
        int j=sa[rrank[i]-1];
        while(s[i+k]==s[j+k])
            k++;
        height[rrank[i]]=k;
    }
}
void ST()
{
    for(int i=1;i<=n;i++)
        st[i][0]=height[i];
    for(int j=1;j<=19;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
int getmin(int x,int y)
{
    int l=rrank[x];
    int r=rrank[y];
    if(l>r)
        swap(l,r);
    l++;
    int k=log2(r-l+1);
    return min(st[l][k],st[r-(1<<k)+1][k]);
}
int a[maxn];
int main()
{
    int cnt=1;
    while(scanf("%s",s)&&strcmp(s,"#"))
    {
        n=strlen(s);
        s[n]=0;
        n++;
        SA(128);
        n--;
        getheight();
        ST();
        int ans=0;
        string ss;
        int len=0;
        for(int i=1;i<n;i++)
            for(int j=0;j+i<n;j+=i)
        {
            int ttmp;
            int tmp=ttmp=getmin(j,j+i);
            int k=j-(i-tmp%i);
            int mid=j;
            tmp/=i;
            if(k>=0&&ttmp%i)
                if(getmin(k,k+i)>=ttmp)
                    {
                        tmp++;
                        mid=k;

                    }
            if(ans<tmp+1)
            {
                ans=tmp+1;
                len=0;
                a[len++]=i;
            }
            if(ans==tmp+1)
            {
                a[len++]=i;
            }
        }
        int flag=0,pos;
        for(int i=1;i<=n&&!flag;i++)
            for(int j=0;j<=len;j++)
        {
            int mid=a[j];
            if(getmin(sa[i],sa[i]+mid)>=(ans-1)*mid)
            {
                flag=mid;
                pos=sa[i];
                break;
            }
        }
        //cout << pos << flag <<endl;
        for(int i=0;i<flag*ans;i++)
            ss+=s[pos+i];
        printf("Case %d: ",cnt++);
        //cout << ans << endl;
        cout << ss << endl;
    }

}

兩串字符中都出現過的最長子串

題意:給你兩串字符,要你找出在這兩串字符中都出現過的最長子串.........

思路:先用個分隔符將兩個字符串連接起來,再用後綴數組求出height數組的值,找出一個height值最大並且i與i-1的sa值分別在兩串字符中就好.....

Sample Input

yeshowmuchiloveyoumydearmotherreallyicannotbelieveit
yeaphowmuchiloveyoumydearmother

Sample Output

27
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
#define maxn 222222
int n;
int ansnum;
int s[maxn];
int c[maxn],sa[maxn],x[maxn],y[maxn],height[maxn],rrank[maxn],st[maxn][20],col[111],vis[111];
int ans[maxn],re[maxn];
void SA(int m)
{
    for(int i=0;i<m;i++)
        c[i]=0;
    for(int i=0;i<n;i++)
        c[x[i]=s[i]]++;
    for(int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int p=0;
        for(int i=n-k;i<n;i++)
            y[p++]=i;
        for(int i=0;i<n;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
        for(int i=0;i<m;i++)
            c[i]=0;
        for(int i=0;i<n;i++)
            c[x[y[i]]]++;
        for(int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        if(p>=n)
            break;
        m=p;
    }
}
void getheight()
{
    int k=0;
    for(int i=0;i<=n;i++)
        rrank[sa[i]]=i;
    for(int i=0;i<n;i++)
    {
        if(k)
            k--;
        int j=sa[rrank[i]-1];
        while(s[i+k]==s[j+k])
            k++;
        height[rrank[i]]=k;
    }
}
void ST()
{
    for(int i=1;i<=n;i++)
        st[i][0]=height[i];
    for(int j=1;j<=19;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
int getmin(int x,int y)
{
    int l=rrank[x];
    int r=rrank[y];
    if(l>r)
        swap(l,r);
    l++;
    int k=log2(r-l+1);
    return min(st[l][k],st[r-(1<<k)+1][k]);
}
int check ()
{
    int mid=0;
    for(int i=2;i<=n;i++)
    {
        if(col[sa[i]]!=col[sa[i-1]])
            mid=max(mid,height[i]);
    }
    return mid;
}
int a[maxn];
int main()
{
    char smid[maxn];
    n=0;
    for(int i=0;i<2;i++)
        {
            scanf("%s",smid);
            int len=strlen(smid);
            for(int j=0;j<len;j++)
                {
                    col[n]=i;
                    s[n++]=smid[j];
                }
            s[n++]=129+i;
        }
    s[n-1]=0;
    SA(300);
    n--;
    getheight();
    cout << check() <<endl;
}

 

求出長度不小於k的公共子串個數

題意:給定兩個字符串 A 和 B ,求長度不小於 k 的公共子串的個數(可以相同)。

計算A的某個後綴與B的某個後綴的最長公共前綴長度,如果長度L大於k,則加上L-k+1組。

將兩個字符串連接起來,中間用一個沒有出現的字符分開。(這是一個神奇的做法)

然後通過height數組分組,某個組內的height都是大於等於k的,也就是任意兩個後綴的最長公共前綴都至少爲k。

掃描一遍,遇到一個B的後綴就與之前的A後綴進行統計,求出所有的滿足的組數。但是這樣的做法便是n^2的。

可以發現兩個後綴的最長公共前綴爲這一段的height值的最小值。

可以通過一個單調棧來維護一下,當前要入棧元素如果小於棧底元素,說明之後加入的B後綴與棧底的最長公共前綴是小於等於入棧的。這樣就保證了單調棧內的height值是絕對遞增的,逐漸合併,均攤可以達到o(n)的複雜度。

然後掃描兩遍即可

Sample Input

2
aababaa
abaabaa
1
xx
xx
0

Sample Output

22
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
#define maxn 222222
long long int n;
long long int ansnum;
long long int s[maxn];
long long int c[maxn],sa[maxn],x[maxn],y[maxn],height[maxn],rrank[maxn],st[maxn][20],col[111],vis[111];
long long int ans[maxn],re[maxn];
void SA(long long int m)
{
    for(long long int i=0;i<m;i++)
        c[i]=0;
    for(long long int i=0;i<n;i++)
        c[x[i]=s[i]]++;
    for(long long int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(long long int i=n-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(long long int k=1;k<=n;k<<=1)
    {
        long long int p=0;
        for(long long int i=n-k;i<n;i++)
            y[p++]=i;
        for(long long int i=0;i<n;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
        for(long long int i=0;i<m;i++)
            c[i]=0;
        for(long long int i=0;i<n;i++)
            c[x[y[i]]]++;
        for(long long int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(long long int i=n-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(long long int i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        if(p>=n)
            break;
        m=p;
    }
}
void getheight()
{
    long long int k=0;
    for(long long int i=0;i<=n;i++)
        rrank[sa[i]]=i;
    for(long long int i=0;i<n;i++)
    {
        if(k)
            k--;
        long long int j=sa[rrank[i]-1];
        while(s[i+k]==s[j+k])
            k++;
        height[rrank[i]]=k;
    }
}
void ST()
{
    for(long long int i=1;i<=n;i++)
        st[i][0]=height[i];
    for(long long int j=1;j<=19;j++)
        for(long long int i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
long long int getmin(long long int x,long long int y)
{
    long long int l=rrank[x];
    long long int r=rrank[y];
    if(l>r)
        swap(l,r);
    l++;
    long long int k=log2(r-l+1);
    return min(st[l][k],st[r-(1<<k)+1][k]);
}
long long int sta[maxn];
long long int stb[maxn];
long long int checka(long long int k)
{
    long long int ss=0;
    long long int top=0;
    long long int ans=0;
    for(long long int i=2;i<=n;i++)
        if(height[i]>=k)
        {
            long long int cnt=0;
            if(col[sa[i-1]]==0)
            {
                cnt++;
                ss+=height[i]-k+1;
            }
            while(top>0&&height[i]<=sta[top-1])
            {
                top--;
                ss-=stb[top]*(sta[top]-height[i]);
                cnt+=stb[top];
            }
            sta[top]=height[i];
            stb[top++]=cnt;
            if(col[sa[i]]==1)
                ans+=ss;
        }
        else
        {
            top=ss=0;
        }
        return ans;
}
long long int checkb(long long int k)
{
    long long int ss=0;
    long long int top=0;
    long long int ans=0;
    for(long long int i=2;i<=n;i++)
        if(height[i]>=k)
        {
            long long int cnt=0;
            if(col[sa[i-1]]==1)
            {
                cnt++;
                ss+=height[i]-k+1;
            }
            while(top>0&&height[i]<=sta[top-1])
            {
                top--;
                ss-=stb[top]*(sta[top]-height[i]);
                cnt+=stb[top];
            }
            sta[top]=height[i];
            stb[top++]=cnt;
            if(col[sa[i]]==0)
                ans+=ss;
        }
        else
        {
            top=ss=0;
        }
    return ans;
}
char a[maxn];
int main()
{
    long long int k;
    while(scanf("%lld",&k)!=EOF&&k)
    {
        scanf("%s",a);
        long long int lena;
        lena=strlen(a);
        for(long long int i=0;i<lena;i++)
        {
            s[i]=a[i];
            col[i]=0;
        }
        s[lena]='#';
        col[lena]=-1;
        scanf("%s",a);
        long long int lenb;
        lenb=strlen(a);
        for(long long int i=0;i<lenb;i++)
        {
            s[lena+1+i]=a[i];
            col[lena+1+i]=1;
        }
        s[lena+1+lenb]=0;
        n=lena+lenb+2;
        SA(200);
        n--;
        getheight();
        printf("%lld\n",checka(k)+checkb(k));
    }

}

出現在至少k個字符串中的最長子串

給定n個字符串,求出現在多於n/2個字符串中的最大長度的子串。

用樸素的搜索方法肯定不能滿足時間要求。因此還是使用後綴數組。

首先把問題變成判定性問題,即長度爲某個值的滿足條件的子串是否存在。這樣就可以使用二分來求解。

接着是把n個字符串用某個字符連接起來。計算出sa數組和height數組。對於每個需要判定的長度,對height數組進行分組,對每個分組分別進行統計,如果該分組中包含了多於n/2個原串,則滿足條件。

本題要求把所有滿足條件的子串全部打印出來,因此可以把判定過程中得到的最大長度的子串的起始位置存下來,最後打印出來就可以了。

3
abcdefg
bcdefgh
cdefghi
3
xxx
yyy
zzz
0

Sample Output

bcdefg
cdefgh

?
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
#define maxn 111111
int n;
int num,ansnum;
int s[maxn];
int c[maxn],sa[maxn],x[maxn],y[maxn],height[maxn],rrank[maxn],st[maxn][20],col[111],vis[111];
int ans[maxn],re[maxn];
void SA(int m)
{
    for(int i=0;i<m;i++)
        c[i]=0;
    for(int i=0;i<n;i++)
        c[x[i]=s[i]]++;
    for(int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int p=0;
        for(int i=n-k;i<n;i++)
            y[p++]=i;
        for(int i=0;i<n;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
        for(int i=0;i<m;i++)
            c[i]=0;
        for(int i=0;i<n;i++)
            c[x[y[i]]]++;
        for(int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        if(p>=n)
            break;
        m=p;
    }
}
void getheight()
{
    int k=0;
    for(int i=0;i<=n;i++)
        rrank[sa[i]]=i;
    for(int i=0;i<n;i++)
    {
        if(k)
            k--;
        int j=sa[rrank[i]-1];
        while(s[i+k]==s[j+k])
            k++;
        height[rrank[i]]=k;
    }
}
void ST()
{
    for(int i=1;i<=n;i++)
        st[i][0]=height[i];
    for(int j=1;j<=19;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
int getmin(int x,int y)
{
    int l=rrank[x];
    int r=rrank[y];
    if(l>r)
        swap(l,r);
    l++;
    int k=log2(r-l+1);
    return min(st[l][k],st[r-(1<<k)+1][k]);
}
int check (int len)
{
    //cout << len <<endl;
    int cnt=0;
    for(int i=2;i<=n;i++)
    {
        //cout << height[i] << endl;
        if(height[i]<len)
        {
            cnt=0;
            for(int j=0;j<111;j++)
                vis[j]=0;
        }
        else
        {
            if(!vis[col[sa[i]]])
            {
                vis[col[sa[i]]]=1;
                cnt++;
            }
            if(!vis[col[sa[i-1]]])
            {
                vis[col[sa[i-1]]]=1;
                cnt++;
            }
        }if(cnt>num/2)
        return 1;
    }

    return 0;

}
int a[maxn];
int main()
{
    int lineflag=0;
    while(scanf("%d",&num)!=EOF&&num)
    {
        if(lineflag)
        {
            cout << endl;
        }
        lineflag=1;
        char smid[maxn];
        n=0;
        for(int i=0;i<num;i++)
            {
                scanf("%s",smid);
                int len=strlen(smid);
                for(int j=0;j<len;j++)
                    {
                        col[n]=i;
                        s[n++]=smid[j];
                    }
                s[n++]=129+i;
            }
        s[n-1]=0;
        SA(300);
        n--;
        getheight();
        int l=1;
        int r=maxn;
        ansnum=0;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(check(mid))
            {
                ansnum=mid;
                l=mid+1;
            }
            else
                r=mid-1;
        }
        //cout << ansnum <<endl;
        if(ansnum==0)
        {
            cout << "?" << endl;
            continue;
        }
        for(int i=0;i<111;i++)
        {
            vis[i]=0;
        }
        int cnt=0;
        int flag=1;
        for(int i=2;i<=n;i++)
        {

            if(height[i]<ansnum)
            {
                for(int i=0;i<111;i++)
                    vis[i]=0;
                cnt=0;
                flag=1;
            }
            else
            {
                if(!vis[col[sa[i]]])
                {
                    vis[col[sa[i]]]=1;
                    cnt++;
                }
                if(!vis[col[sa[i-1]]])
                {
                    vis[col[sa[i-1]]]=1;
                    cnt++;
                }
                if(cnt>num/2&&flag)
                {
                    for(int j=0;j<ansnum;j++)
                        printf("%c",s[sa[i]+j]);
                    cout <<endl;

                    flag=0;
                }
            }
        }
    }

}

最長公共子串

題意:給定n個字符串,求出現或反轉後出現在每個字符串中的最長子串。 

思路:先將每個字符串都反過來寫一遍,中間用一個互不相同的 


且沒有出現在字符串中的字符隔開,再將n個字符串全部連起來,中間也是用一 


個互不相同的且沒有出現在字符串中的字符隔開,求後綴數組。然後二分答案, 


再將後綴分組。判斷的時候,要看是否有一組後綴在每個原來的字符串或反轉後 


的字符串中出現。

Sample Input

2
3
ABCD
BCDFF
BRCD
2
rose
orchid

Sample Output

2
2 
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
#define maxn 222222
long long int n;
long long int ansnum;
long long int s[maxn];
long long int c[maxn],sa[maxn],x[maxn],y[maxn],height[maxn],rrank[maxn],st[maxn][20],col[maxn],vis[111];
long long int ans[maxn],re[maxn];
void SA(long long int m)
{
    for(long long int i=0;i<m;i++)
        c[i]=0;
    for(long long int i=0;i<n;i++)
        c[x[i]=s[i]]++;
    for(long long int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(long long int i=n-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(long long int k=1;k<=n;k<<=1)
    {
        long long int p=0;
        for(long long int i=n-k;i<n;i++)
            y[p++]=i;
        for(long long int i=0;i<n;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
        for(long long int i=0;i<m;i++)
            c[i]=0;
        for(long long int i=0;i<n;i++)
            c[x[y[i]]]++;
        for(long long int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(long long int i=n-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(long long int i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        if(p>=n)
            break;
        m=p;
    }
}
void getheight()
{
    long long int k=0;
    for(long long int i=0;i<=n;i++)
        rrank[sa[i]]=i;
    for(long long int i=0;i<n;i++)
    {
        if(k)
            k--;
        long long int j=sa[rrank[i]-1];
        while(s[i+k]==s[j+k])
            k++;
        height[rrank[i]]=k;
    }
}
void ST()
{
    for(long long int i=1;i<=n;i++)
        st[i][0]=height[i];
    for(long long int j=1;j<=19;j++)
        for(long long int i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
long long int getmin(long long int x,long long int y)
{
    long long int l=rrank[x];
    long long int r=rrank[y];
    if(l>r)
        swap(l,r);
    l++;
    long long int k=log2(r-l+1);
    return min(st[l][k],st[r-(1<<k)+1][k]);
}
int m;
int check(int len)
{
    //cout << len <<endl;
    int cnt=0;
    for(int i=2;i<=n;i++)
    {
        if(height[i]>=len)
        {
            if(!vis[col[sa[i]]])
            {
                vis[col[sa[i]]]=1;
                cnt++;
            }
            if(!vis[col[sa[i-1]]])
            {
                vis[col[sa[i-1]]]=1;
                cnt++;
            }
            if(cnt>=m)
                return 1;
        }
        else
        {
//            if(cnt>=m)
//                return 1;
            cnt=0;
            for(int i=0;i<111;i++)
                vis[i]=0;
        }
    }
//    if(cnt>=m)
//                return 1;
    return 0;
}
char ss[maxn];
int main()
{
    int T;
	scanf("%d",&T);
	while(T--)
	{
		n=0;
		int p=0;
		scanf("%d",&m);
		for(int i=1;i<=m;i++)
		{
			scanf("%s",ss);
		    int len=strlen(ss);
		    for(int j=0;j<len;j++)
            {
                col[n]=i;
                s[n++]=ss[j];
            }
            s[n++]=128+p++;
            for(int j=len-1;j>=0;j--)
            {
                col[n]=i;
                s[n++]=ss[j];
            }
            s[n++]=128+p++;
		}
		s[n-1]=0;
		SA(600);
		n--;
		getheight();
        int l=1;
        int r=111;
        int ans=0;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(check(mid))
            {
                ans=mid;
                l=mid+1;
            }
            else
                r=mid-1;
        }
        if(m==1)
            ans=strlen(ss);
        cout << ans <<endl;
	}
}

所有前綴出現次數和 

Sample Input

aaa
abab

Sample Output

6
6
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
#define maxn 222222
int n;
char s[maxn];
int c[maxn],sa[maxn],x[maxn],y[maxn],height[maxn],rrank[maxn],st[maxn][20];
void SA(int m)
{
    for(int i=0;i<m;i++)
        c[i]=0;
    for(int i=0;i<n;i++)
        c[x[i]=s[i]]++;
    for(int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int p=0;
        for(int i=n-k;i<n;i++)
            y[p++]=i;
        for(int i=0;i<n;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
        for(int i=0;i<m;i++)
            c[i]=0;
        for(int i=0;i<n;i++)
            c[x[y[i]]]++;
        for(int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        if(p>=n)
            break;
        m=p;
    }
}
void getheight()
{
    int k=0;
    for(int i=0;i<=n;i++)
        rrank[sa[i]]=i;
    for(int i=0;i<n;i++)
    {
        if(k)
            k--;
        int j=sa[rrank[i]-1];
        while(s[i+k]==s[j+k])
            k++;
        height[rrank[i]]=k;
    }
}
void ST()
{
    for(int i=1;i<=n;i++)
        st[i][0]=height[i];
    for(int j=1;j<=19;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
int getmin(int x,int y)
{
    int l=rrank[x];
    int r=rrank[y];
    if(l>r)
        swap(l,r);
    l++;
    int k=log2(r-l+1);
    return min(st[l][k],st[r-(1<<k)+1][k]);
}
int main()
{
    while(scanf("%s",s)!=EOF)
    {
        n=strlen(s);
        s[n]=0;
        n++;
        SA(200);
        n--;
        getheight();
        ST();
        int ans=0;
        for(int i=1;i<n;i++)
        {
            ans=(ans+getmin(0,i))%256;
        }
        ans=(ans+n)%256;
        cout << ans << endl;
    }

}

求最長公共前綴

題目的本質就是求輸入的相鄰的兩個串的最長公共字串,有sa來解求得height數組,然後用rmq算法查詢。

Sample Input

frcode
2
0 6
0 6
unitedstatesofamerica
3
0 6
0 12
0 21
myxophytamyxopodnabnabbednabbingnabit
6
0 9
9 16
16 19
19 25
25 32
32 37

Sample Output

14 12
42 31
43 40
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
#define maxn 111111
long long int n;
char s[maxn];
long long int c[maxn],sa[maxn],x[maxn],y[maxn],height[maxn],rrank[maxn],st[maxn][20];
void SA(long long int m)
{
    for(long long int i=0;i<m;i++)
        c[i]=0;
    for(long long int i=0;i<n;i++)
        c[x[i]=s[i]]++;
    for(long long int i=1;i<m;i++)
        c[i]+=c[i-1];
    for(long long int i=n-1;i>=0;i--)
        sa[--c[x[i]]]=i;
    for(long long int k=1;k<=n;k<<=1)
    {
        long long int p=0;
        for(long long int i=n-k;i<n;i++)
            y[p++]=i;
        for(long long int i=0;i<n;i++)
            if(sa[i]>=k)
                y[p++]=sa[i]-k;
        for(long long int i=0;i<m;i++)
            c[i]=0;
        for(long long int i=0;i<n;i++)
            c[x[y[i]]]++;
        for(long long int i=1;i<m;i++)
            c[i]+=c[i-1];
        for(long long int i=n-1;i>=0;i--)
            sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(long long int i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        if(p>=n)
            break;
        m=p;
    }
}
void getheight()
{
    long long int k=0;
    for(long long int i=0;i<=n;i++)
        rrank[sa[i]]=i;
    for(long long int i=0;i<n;i++)
    {
        if(k)
            k--;
        long long int j=sa[rrank[i]-1];
        while(s[i+k]==s[j+k])
            k++;
        height[rrank[i]]=k;
    }
}
void ST()
{
    for(long long int i=1;i<=n;i++)
        st[i][0]=height[i];
    for(long long int j=1;j<=19;j++)
        for(long long int i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
long long int getmin(long long int x,long long int y)
{
    long long int l=rrank[x];
    long long int r=rrank[y];
    if(l>r)
        swap(l,r);
    l++;
    long long int k=log2(r-l+1);
    return min(st[l][k],st[r-(1<<k)+1][k]);
}
int main()
{
    while(scanf("%s",s)!=EOF)
    {
        n=strlen(s);
        s[n]=0;
        n++;
        SA(300);
        n--;
        getheight();
        ST();
        long long int t;
        scanf("%lld",&t);
        long long int ans1=0,ans2=0;
        long long int prel,prer;
        scanf("%lld%lld",&prel,&prer);
        ans1+=prer-prel+1;
        ans2+=prer-prel+3;
        t--;
        while(t--)
        {
            long long int l,r;
            scanf("%lld%lld",&l,&r);
            ans1+=r-l+1;
            ans2+=r-l+3;
            long long int mid=min(getmin(prel,l),min(r-l,prer-prel));
            if(prel==l)
                mid=min(r-l,prer-prel);
            //cout << mid <<endl;
            ans2-=mid;
            while(mid>=10)
            {
                ans2++;
                mid/=10;
            }
            prel=l;
            prer=r;
        }
        cout << ans1 << " " << ans2 << endl;
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章