字符串模板(後綴數組、後綴自動機、迴文樹)

再整理一遍板子

後綴數組

例題cf432D:問對字符串s,對於所有的前綴,當它等於同長度後綴時,這個子串一共在s中出現多少次。

後綴數組求lcp是logn,顯然直接二分即可。複雜度nlogn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+2;
int n,sa[N],rnk[N],height[N];
char s[N];
int st[N][20];
//sa數組表示字典序爲i的後綴是誰,rnk數組表示某個後綴的排名(1~n)
void buildSA(int m=128){
    int cnt[N],rnk1[N],rnk2[N],tmpSA[N];    //cnt[i]用來記錄rnk小於等於i的子串已經有多少個了,這樣可以直接用cnt[rnk[i]]更新sa
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;++i)
        cnt[(int)s[i]]++;
    for(int i=1;i<=m;i++)
        cnt[i]+=cnt[i-1];
    for(int i=1;i<=n;i++)
        rnk[i]=cnt[(int)s[i]];

    for(int l=1;l<n;l<<=1){
        for(int i=1;i<=n;++i){
            rnk1[i]=rnk[i];
            rnk2[i]=(i+l<=n?rnk[i+l]:0); //如有缺失,名次按0算,即較短的佔優勢,它和較長的比較時缺失部分的優先級按最高算
        }
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;++i)
            cnt[rnk2[i]]++;
        for(int i=1;i<=n;++i)    //小於等於當前rnk的共有幾個後綴
            cnt[i]+=cnt[i-1];
        for(int i=n;i;--i)         //tmpSA這裏按rnk2名次記錄的後綴
            tmpSA[cnt[rnk2[i]]--]=i;    //--是爲了區分rnk相同的串,後訪問的排序靠前一些
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;++i)
            cnt[rnk1[i]]++;
        for(int i=1;i<=n;++i)
            cnt[i]+=cnt[i-1];
        for(int i=n;i;--i)
            sa[cnt[rnk1[tmpSA[i]]]--]=tmpSA[i];    //是按照tmpSA的逆序計算,也就是rnk2較大的對應的cnt值會大一些,排在rnk1相同而rnk2較小的後面
        //cnt代表每個桶的大小
        bool uniq=true;     //如果構建完rnk數組還是true,說明所有後綴的大小已經區分出來了,沒有排名相同的後綴了,可以退出
        rnk[sa[1]]=1;       //構建新的rank數組
        for(int i=2;i<=n;++i){
            rnk[sa[i]]=rnk[sa[i-1]];
            if(rnk1[sa[i]]==rnk1[sa[i-1]]&&rnk2[sa[i]]==rnk2[sa[i-1]])
                uniq=false;     //這裏不能break,因爲還要繼續把當前rank數組算完
            else
                rnk[sa[i]]++;
        }
        if(uniq)
            break;
    }
}
//height[i]表示位於sa[i-1]和sa[i]的兩個後綴的最長前綴
//有性質height[rnk[i]]>=height[rnk[i-1]]-1,即字符串向後推一位,height值最多減小1
void getHeight() {
    for(int i=1,k=0;i<=n;++i){
        if(k) --k;        //新的長度不會比k-1小,是結論
        int j=sa[rnk[i]-1]; //j是排序剛好比i小1的後綴
        while(s[i+k]==s[j+k])
            k++;
        height[rnk[i]]=k;
    }
}

//滿足lcp(l,r)=min{height[i+1],...height[r]},故是RMQ問題
//st[i][j]表示[i,i+(1<<j)-1]的最小height,[i,i+(1<<j)]的lcp,這一點注意一下
void initST(){    //用ST表進行RMQ
    for(int i=1;i<n;i++)
        st[i][0]=height[i+1];
    for(int l=1;(1<<l)<=n-1;l++){
        for(int i=1;i+(1<<l)-1<=n;i++)
            st[i][l]=min(st[i][l-1],st[i+(1<<(l-1))][l-1]);
    }
}

//lcp:最長公共前綴,此處指sa[l]和sa[r]的最長公共前綴
int lcp(int l,int r){
    if(l==r)
        return n-sa[l]+1;
    if(l>r)
        swap(l,r);
    int k=0;
    while((1<<(k+1))+1<=r-l+1) ++k;
    return min(st[l][k],st[r-(1<<k)][k]);
}
vector<pair<int,int>> ans;
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    buildSA();
    getHeight();
    initST();
    for(int len=1;len<=n;len++){
        int a=rnk[1],b=rnk[n-len+1];
        if(lcp(a,b)<len)
            continue;
        if(a>b) swap(a,b);
        int res=b-a+1;
        int l=1,r=a;
        while(l<=r){
            int mid=(l+r)/2;
            if(lcp(mid,a)>=len)
                r=mid-1;
            else
                l=mid+1;
        }
        res+=max(a-l,0);
        l=b,r=n;
        while(l<=r){
            int mid=(l+r)/2;
            if(lcp(mid,b)>=len)
                l=mid+1;
            else
                r=mid-1;
        }
        res+=max(r-b,0);
        ans.push_back(make_pair(len,res));
        //printf("%d %d\n",len,res);
    }
    cout<<ans.size()<<endl;
    for(auto p:ans)
        cout<<p.first<<' '<<p.second<<endl;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章