zoukankan      html  css  js  c++  java
  • 【浮*光】#字符串# 字符串の相关练习题

    Trie树

    https://www.cnblogs.com/FloraLOVERyuuji/p/10456880.html


    KMP算法

    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    #include <map>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    /*【p3290】围棋 // 轮廓线DP + 容斥思想 + KMP
    每个格子可以是黑、白、空,求n*m的棋盘上包含‘给定的2*c模板块’的方案数。*/
    
    /*【分析】考虑容斥,ans=3^(n*m)-不含2*c的方案数。
    设f[i][j][S][x][y]表示填到了(i,j),
    轮廓线上每个位置作为末尾、是否完全匹配第一个串的状态为S,
    与第一个串kmp到了x,与第二个串kmp到了y的方案数。 */
    
    void reads(int &x){ //读入优化(正负整数)
        int fx_=1;x=0;char ch_=getchar();
        while(ch_<'0'||ch_>'9'){if(ch_=='-')fx_=-1;ch_=getchar();}
        while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();}
        x*=fx_; //正负号
    }
    
    const int mod=1000000007; int i,j,k,c,S,x,y;
    
    int T,n,m,nxt[9],ta[9][3],tb[9][3],na,nb,U,E;
    
    int f[1024][6][6],g[1024][6][6],ans; char a[9],b[9];
    
    int id(char x){ if(x=='B') return 0; return (x=='W')?1:2; }
    
    void up(int&x,int y){ x+=y; if(x>=mod) x-=mod; }
    
    void clear(){ for(S=0;S<U;S++) for(x=0;x<c;x++) for(y=0;y<c;y++) g[S][x][y]=0; }
    
    void copy(){ for(S=0;S<U;S++) for(x=0;x<c;x++) for(y=0;y<c;y++) f[S][x][y]=g[S][x][y]; }
    
    int main(){
        reads(n),reads(m),reads(c),reads(T); //T:模板的数量
        while(T--){ scanf("%s%s",a+1,b+1);
            
            for(i=1;i<=c;i++) a[i]=id(a[i]),b[i]=id(b[i]);
            
            for(nxt[1]=j=0,i=2;i<=c;nxt[i++]=j) //模板第一行自我匹配
             { while(j&&a[j+1]!=a[i]) j=nxt[j]; if(a[j+1]==a[i]) j++; }
            
            for(na=nxt[c],i=0;i<c;i++) 
              for(j=0;j<3;j++){ for(k=i;k&&a[k+1]!=j;k=nxt[k]); 
                if(a[k+1]==j) k++; ta[i][j]=k; }
            
            for(nxt[1]=j=0,i=2;i<=c;nxt[i++]=j) //模板第二行自我匹配
             { while(j&&b[j+1]!=b[i]) j=nxt[j]; if(b[j+1]==b[i]) j++; }
    
            for(nb=nxt[c],i=0;i<c;i++) 
              for(j=0;j<3;j++){ for(k=i;k&&b[k+1]!=j;k=nxt[k]); 
                if(b[k+1]==j) k++; tb[i][j]=k; }
            
            U=1<<(m-c+1); //匹配的状态
            
            for(S=0;S<U;S++) for(x=0;x<c;x++) for(y=0;y<c;y++) f[S][x][y]=0;
    
            for(f[0][0][0]=i=1;i<=n;i++){ clear();
                for(S=0;S<U;S++) for(x=0;x<c;x++) for(y=0;y<c;y++)
                    if(f[S][x][y]) up(g[S][0][0],f[S][x][y]); copy();
                for(j=1;j<=m;j++){ clear(); for(S=0;S<U;S++) 
                  for(x=0;x<c;x++) for(y=0;y<c;y++) if(f[S][x][y]) 
                    for(k=0;k<3;k++){ E=S; if(j>=c) if(S>>(j-c)&1) E^=1<<(j-c);
                      int A=ta[x][k]; if(A==c) E|=1<<(j-c),A=na;
                      int B=tb[y][k]; if(B==c){ if(S>>(j-c)&1) continue; B=nb; }
                      up(g[E][A][B],f[S][x][y]); } copy();
                }
            } for(ans=1,i=n*m;i;i--) ans=3LL*ans%mod;
    
            for(S=0;S<U;S++) for(x=0;x<c;x++) for(y=0;y<c;y++)
                up(ans,mod-f[S][x][y]); printf("%d
    ",ans);
        }
    }
    【p3290】围棋 // 轮廓线DP + 容斥思想 + KMP
    #include <cstdio>
    #include <cstring>
    
    /* 【P2353】背单词 // 前缀和 + kmp统计匹配个数
    长度为N的文章,M个单词。Q个问题。
    询问文章的区间L..R中,单词总共出现过多少次。*/
    
    /* 由于M很小,可以进行M次kmp,统计出M个前缀和。
    每次输出时把 M 个前缀和扫一遍,注意区间的开闭问题。*/
    
    #define Max 1000019
    
    void read (int &now){
        now = 0; register char word = getchar ();
        while (word > '9' || word < '0') word = getchar ();
        while (word >= '0' && word <= '9')
            now = now * 10 + word - '0', word = getchar ();
    }
    
    int __next[Max];
    
    void Get_Next (char *line){
        __next[0] = -1;
        for (int pos_1 = 0, pos_2 = -1, Len = strlen (line); pos_1 < Len; )
            if (pos_2 == -1 || line[pos_1] == line[pos_2]){
                pos_1 ++; pos_2 ++;  __next[pos_1] = pos_2;
            } else pos_2 = __next[pos_2];
    }
    
    int __sum[Max][Max / 100000 + 1];
    
    void Kmp (char *line, char *__txt, int number){
        for (int Len_txt = strlen (__txt), Len = strlen (line), 
          pos_1 = 0, pos_2 = 0; pos_1 <= Len_txt; ){
            if (pos_2 == -1 || __txt[pos_1] == line[pos_2])
             { pos_1 ++; pos_2 ++; } else pos_2 = __next[pos_2];
            if (pos_2 == Len) __sum[pos_1][number]++,pos_2=__next[pos_2];
        } //记录编号为number的单词在文章中出现的位置(串末尾)前缀和
    }
    
    char __txt[Max];
    
    int length[Max];
    char line[Max];
    
    int main(){
        
        int N, M; read (N); read (M);
    
        scanf ("%s", __txt);
    
        int Len_txt = strlen (__txt);
    
        for (int i = 1; i <= N; i ++) {
            scanf ("%s", line); //单词
            Get_Next (line); //kmp的自我匹配
            Kmp (line, __txt, i);
            length[i] = strlen (line);
        }
    
        for (int i = 1; i <= Len_txt; i ++)
         // 把每个模式串(单词)的前缀和分开存 
            for (int j = 1; j <= N; j ++)
                __sum[i][j] += __sum[i - 1][j];
    
        for (int i = 1, x, y, Answer; i <= M; i ++){
            read (x); read (y); Answer = 0;
            for (int j = 1; j <= N; j ++)
                if (x - 1 <= y - length[j]) //区间>单词长度
                    Answer += __sum[y][j] - __sum[x + length[j] - 2][j];
                //第一次出现的末尾只可能是x+len[j]-1
                //用差分的思想,应该要减x+len[j]-2的sum值
            printf ("%d
    ", Answer);
        }
    }
    【P2353】背单词 // 前缀和 + kmp统计匹配单词的个数

    Manacher


    AC自动机


    后缀数组

    int n,m,a[maxn],rank[maxn],sa[maxn],
        tp[maxn],tax[maxn],height[maxn];
    
    void qsort(){
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[rank[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    }
    
    void SuffixSort(){
        for(int i=1;i<=n;i++) rank[i]=a[i],tp[i]=i; //a[i]是原串数字
        m=519; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); swap(rank,tp); rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]
                   &&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        }
    }
    
    void getH(){ //height[]
        int k=0; for(int i=1;i<=n;i++){
            if(k) k--; int j=sa[rank[i]-1];
            while(a[i+k]==a[j+k]) k++; height[rank[i]]=k;
        }
    }    
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <stack>
    #include <queue>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    const int maxn=5000019; int n,m; char s[maxn];
    
    int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];
    
    void qsort(){
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[rank[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    }
    
    void SuffixSort(){
        for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
        m=519; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); // swap(rank,tp); //第二次基数排序
            for(int i=1;i<=n;i++) b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        }
    }
    
    int main(){
        scanf("%s",s+1); n=strlen(s+1); SuffixSort(); 
        for(int i=1;i<=n;i++) cout<<sa[i]<<" ";
    }
    【p3809】模板-后缀排序
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<set>
    using namespace std;
    typedef long long ll;
    
    /*【p4341】外星联络
    求所有出现次数>1的子串出现的次数,子串按照字典序排序。 */
    
    /*【后缀数组】先求出sa和height数组,然后枚举。
    由于字符串有一个性质:字符串的所有子串就是所有后缀的所有前缀。
    可以枚举每个字符向后延续的长度。然后向右循环,看有多少个height大于该长度。
    需要注意:(1)枚举长度时要从height+1开始,因为前面的都已经处理过。
             (2)循环时不能向左循环,因为左边的已经找过。
    最后判断一下出现次数是否大于1即可。 */
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fx; //正负号
    }
    
    const int N=500019; int n,m; char ss[N];
    
    int rank[N],b[N],sa[N],tp[N],tax[N],height[N];
    
    void qsort(){
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[rank[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    }
    
    void get_height(){ 
        int k=0; //求height[]
        for(int i=1;i<=n;i++){
            if(k) k--; int j=sa[rank[i]-1];
            while(ss[i+k]==ss[j+k]) k++; height[rank[i]]=k; 
        }
    }
    
    void get_sa(){
        for(int i=1;i<=n;i++) rank[i]=ss[i]-'0'+1,tp[i]=i;
        m=127; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); for(int i=1;i<=n;i++) // swap(rank,tp);
                b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]
                    &&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        } get_height();
    }
    
    int main(){
        scanf("%d%s",&n,ss+1); get_sa();
        for(int i=2;i<=n;i++){ //按排名枚举子串(第一个舍去)  
            for(int j=height[i-1]+1;j<=height[i];j++){
                //j初始值为'排名为i-1的子串lcp',要<=当前子串串长 
                int k=i; while(height[k]>=j) k++;
                //↑↑寻找lcp值>=j的子串最后一次出现的位置
                printf("%d
    ",k-i+1); //重复出现的次数
            }
        }
    }
    【p4341】外星联络 //求所有出现次数>1的子串出现的次数

    这题也可以用 Trie 水过去... 代码如下...

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<set>
    using namespace std;
    typedef long long ll;
    
    const int maxn = 5000019;
    
    char ch[3000];
    
    int son[maxn][2],sz[maxn],tot=1,n;
    
    inline void ins(const char*ch){
        int rt=1;
        for(;*ch;++ch){
            int&x=son[rt][*ch-48];
            if(!x) x=++tot;
            ++sz[rt=x];
        }
    }
    
    inline void dfs(int rt){
        if(sz[rt]>1) cout << sz[rt] << '
    ';
        if(son[rt][0]) dfs(son[rt][0]);
        if(son[rt][1]) dfs(son[rt][1]); }
        
    int main(){ cin >> n >> ch; for(int i=0;i<n;++i)ins(ch+i); dfs(1); }
    【p4341】外形联络 //代码很短,实现很简单的Trie
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <stack>
    #include <queue>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    /*【p2743】乐曲主题 [不可重叠最长重复子串] //后缀数组 + 差分转化 + 二分答案
    给定一个序列,求等价的最长子串,且长度大于5,不可重叠。
    等价的定义:对序列整体加上某个数值后与另一个序列完全相等。 */
    
    /*【分析】原长度为l+1的等价序列 <=> 差分后长度为l的相等序列。
    即:求不可重叠的最长重复子串。二分答案k,问题转换为判定是否存在长度为k的最长子串。*/
    
    int read(){ int x=0,f=1;char ss=getchar();
      while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
      while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}return x*f;} 
    
    const int maxn=50019;
    
    int n,m,a[maxn],rank[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];
    
    void qsort(){
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[rank[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    }
    
    void SuffixSort(){
        for(int i=1;i<=n;i++) rank[i]=a[i],tp[i]=i;
        m=519; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); swap(rank,tp); rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        }
    }
    
    void getH(){ //height[]
        int k=0; for(int i=1;i<=n;i++){
            if(k) k--; int j=sa[rank[i]-1];
            while(a[i+k]==a[j+k]) k++; height[rank[i]]=k;
        }
    }
    
    int check(int x){
        int mx=sa[1],mi=sa[1];
        for(int i=2;i<=n;i++){
            if(height[i]<x) mx=mi=sa[i];
            else{ if(sa[i]<mi) mi=sa[i];
                  if(sa[i]>mx) mx=sa[i];
                  if(mx-mi>x) return true; }
        } return false;
    }
    
    int main(){
        while(scanf("%d",&n)!=EOF){
            if(n==0) break;
            for(int i=1;i<=n;i++) a[i]=read();
            for(int i=1;i<n;i++) a[i]=a[i+1]-a[i]+90;
            n--; //差分数组长度少1
    
            SuffixSort(); getH(); //后缀排序+height数组(连续后缀的最长公共前缀)
    
            int ans=0,L=0,R=n,mid; //二分答案+height分组判断
            while(L<R){ mid=L+R>>1; if(check(mid)) ans=mid,L=mid+1; else R=mid; }
            if(ans+1<5) printf("0
    "); else printf("%d
    ",ans+1);
        }
    }
    【p2743】乐曲主题 //后缀数组 + 差分转化 + 二分答案

     【不可重叠最长重复子串】 二分答案k,问题转换为判定是否存在长度为k的最长子串。

    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <stack>
    #include <queue>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    /*【p2852】牛奶模式 [可重叠的k次最长重复子串]
    给定一个序列,求序列中至少出现 k 次的可重叠的最长子串。*/
    
    /*【分析】二分答案长度l,对height数组进行分组。
    即:将height不小于l的放到同一组中。对于不同的height判断是否有一组大小>=k。*/
    
    /*【关于height数组的分组】其实是把n个后缀根据算出来的height分组。
    sa数组是按后缀排名的,如果几个后缀有部分公共前缀,那么在后缀排名上一定是(字典序)挨在一起的。
    对于不同的公共前缀,可以把后缀排名里面[挨在一起、公共前缀长度大于二分的值]的,分为一组。
    这一组里满足有长度大于二分值的公共字串(即后缀的公共前缀),再检查组内是不是有超过k个后缀。*/
    
    const int maxn=50019; int n,m,k,s[maxn];
    
    struct node{ int d,id; }a[maxn];
    
    bool cmp(node a,node b){ return a.d<b.d; }
    
    int rank[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];
    
    void qsort(){
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[rank[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    }
    
    void SuffixSort(){
        for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
        m=519; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); swap(rank,tp); rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        }
    }
    
    void getH(){ //height[]
        int k=0; for(int i=1;i<=n;i++){
            if(k) k--; int j=sa[rank[i]-1];
            while(s[i+k]==s[j+k]) k++; height[rank[i]]=k;
        }
    }
    
    bool check(int mid){
        int cnt=0;
        for(int i=2;i<=n;i++){
            if(height[i]<mid) cnt=0; //另外的组
            else if(++cnt>=k-1) return true;
            //k-1个height元素表示k个后缀串
        } return false;
    }
    
    int main(){
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++)  scanf("%d",&a[i].d),a[i].id=i;
        sort(a+1,a+n+1,cmp); int cnt=0,lastt=0; //离散化
        for(int i=1;i<=n;i++){ //利用排序连续性,将每个数字缩小
            if(a[i].d==lastt) s[a[i].id]=cnt;
            else lastt=a[i].d,s[a[i].id]=++cnt;
        } SuffixSort(); getH(); int l=1,r=n,ans=0,mid; 
        while(l<=r){ mid=(l+r)>>1; if(check(mid)) ans=mid,l=mid+1; else r=mid-1; }
        printf("%d
    ",ans); return 0; //至少出现k次的可重叠的最长子串
    }
    【p2852】牛奶模式 [可重叠的k次最长重复子串]

    【题意】给定一个序列,求序列中至少出现 k 次的可重叠的最长子串。

    【分析】二分答案最长重复子串的长度l,对height数组进行分组。

    即:将height不小于l的放到同一组中。对于不同的height判断是否有一组大小>=k。

    【关于height数组的分组】其实是把n个后缀根据算出来的height分组。

    sa数组是按后缀排名的,如果几个后缀有部分公共前缀,那么在后缀排名上一定是(字典序)挨在一起的。

    对于不同的公共前缀,可以把后缀排名里面[挨在一起、公共前缀长度大于二分的值]的,分为一组。

    这一组里满足有长度大于二分值的公共字串(即后缀的公共前缀),再检查组内是不是有超过k个后缀。

    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <stack>
    #include <queue>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    /*【SP694】disubstr [子串个数]
    给定一个字符串,求不相同的子串的个数。*/
    
    /*【分析】只考虑后缀的前缀,则排第 k 名的后缀有 n−sa[k]+1 个前缀,
    但其中有 height[k] 个前缀和上一个前缀相等,故有 n−sa[k]+1−height[k] 个子串。 */
    
    const int maxn=50019; int n,m; char s[maxn];
    
    int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];
    
    void qsort(){
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[rank[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    }
    
    void SuffixSort(){
        for(int i=1;i<=n;i++) rank[i]=s[i]-'A'+19,tp[i]=i;
        m=519; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); // swap(rank,tp); 
            for(int i=1;i<=n;i++) b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        }
    }
    
    void getH(){ //height[]
        int k=0; for(int i=1;i<=n;i++){
            if(k) k--; int j=sa[rank[i]-1];
            while(s[i+k]==s[j+k]) k++; height[rank[i]]=k;
        }
    }
    
    int main(){
        int T; scanf("%d",&T); 
        while(T--){
            scanf("%s",s+1); n=strlen(s+1);
            SuffixSort(); getH();
            int ans=0; for(int i=1;i<=n;i++)
                ans+=n-sa[i]+1-height[i];
            printf("%d
    ",ans);
        }
    }
    【SP694】disubstr [不相同子串个数]

    【题意】给定一个字符串,求不相同的子串的个数。

    【分析】只考虑后缀的前缀,则排第 k 名的后缀有 n−sa[k]+1 个前缀,

    但其中有 height[k] 个前缀和上一个前缀相等,故有 n−sa[k]+1−height[k] 个子串。 

    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <stack>
    #include <queue>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    /*【p4051】字符加密 // 破环为链 + 求sa数组 
    把需要加密的信息排成一圈,读出最后一列字符作为加密后的字符串。
    例如‘JSOI07’,可以读作: JSOI07 SOI07J OI07JS I07JSO 07JSOI 7JSOI0 
    把它们按照字符串的大小排序: 07JSOI 7JSOI0 I07JSO JSOI07 OI07JS SOI07J */
    
    /*【分析】把字符串接到自己后面长度变成为2∗len,进行后缀排序。输出每串结尾字符。*/
    
    const int maxn=200019; int n,m; char s[maxn];
    
    int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];
    
    void qsort(){
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[rank[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    }
    
    void SuffixSort(){
        for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
        m=519; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); // swap(rank,tp); 
            for(int i=1;i<=n;i++) b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        }
    }
    
    int main(){
        scanf("%s",s+1); n=strlen(s+1);
        for(int i=1;i<=n;i++) s[i+n]=s[i]; 
        n+=n; SuffixSort(); 
        for(int i=1;i<=n;i++) if(sa[i]<=n/2) 
            putchar(s[sa[i]+n/2-1]); cout<<endl;
    }
    【p4051】字符加密 // 破环为链 + 求sa数组

    【题意】把需要加密的信息排成一圈,读出最后一列字符作为加密后的字符串。

    【分析】把字符串接到自己后面长度变成为2∗len,进行后缀排序。输出每串结尾字符。

    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <stack>
    #include <queue>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    /*【p3181】找相同字符 //求两串中相同子串个数
    求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。*/
    
    //通过一个无用字符拼接两个字符串,求height[],可以求出A、B相同子串。
    
    //找到所有极大的公共串,用分类加减的队列求值。
    
    const int maxn=500019; int n,m,len1,len2; char s[maxn],ss[maxn];
    
    int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];
    
    void qsort(){
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[rank[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    }
    
    void SuffixSort(){
        for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
        m=519; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); // swap(rank,tp); 
            for(int i=1;i<=n;i++) 
                b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]
                    &&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        }
    }
    
    void getH(){ 
        int k=0; //求height[]
        for(int i=1;i<=n;i++){
            if(k) k--; int j=sa[rank[i]-1];
            while(s[i+k]==s[j+k]) k++; height[rank[i]]=k; 
        }
    }
    
    ll sum[maxn],sta[maxn],now[maxn],top=0,ans=0;
    
    void work(){ //找出所有在A、B中的后缀串,分类加减
        for(int i=1;i<=n;i++) sum[i]=sum[i-1]+(sa[i]<=len1);
        top=0,sta[0]=1; for(int i=1;i<=n;i++){
            while(top>0&&height[sta[top]]>height[i]) top--; sta[++top]=i; 
            now[top]=(ll)(sum[i-1]-sum[sta[top-1]-1])*height[i]+now[top-1];
            if(sa[i]>len1+1) ans+=now[top];
        } top=0; for(int i=1;i<=n;i++) sum[i]=sum[i-1]+(sa[i]>len1+1);
        for(int i=1;i<=n;i++){ //top=0相当于清空栈,now[0]一直=0,所以不用清零
            while(top>0&&height[sta[top]]>height[i]) top--; sta[++top]=i; 
            now[top]=(ll)(sum[i-1]-sum[sta[top-1]-1])*height[i]+now[top-1];
            if(sa[i]<=len1) ans+=now[top];
        }
    }
    
    int main(){
        scanf("%s",s+1),n=strlen(s+1),len1=n,s[++n]=127;
        scanf("%s",ss+1),len2=strlen(ss+1); //↑↑中间用一个字符隔开
        for(int i=1;i<=len2;i++) s[++n]=ss[i]; //变成同一串
        SuffixSort(); getH(); work(); cout<<ans<<endl;
    }
    【p3181】找相同字符 //求两串中相同子串个数

    通过一个无用字符拼接两个字符串,求height[ ],可以求出A、B相同子串。

    找到所有极大的公共串,用 分类加减的队列 求出 总体子串个数。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<set>
    using namespace std;
    typedef long long ll;
    
    /*【p4341】外星联络
    求所有出现次数>1的子串出现的次数,子串按照字典序排序。 */
    
    /*【后缀数组】先求出sa和height数组,然后枚举。
    由于字符串有一个性质:字符串的所有子串就是所有后缀的所有前缀。
    可以枚举每个字符向后延续的长度。然后向右循环,看有多少个height大于该长度。
    需要注意:(1)枚举长度时要从height+1开始,因为前面的都已经处理过。
             (2)循环时不能向左循环,因为左边的已经找过。
    最后判断一下出现次数是否大于1即可。 */
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fx; //正负号
    }
    
    const int N=500019; int n,m; char ss[N];
    
    int ranks[N],b[N],sa[N],tp[N],tax[N],height[N];
    
    void qsort(){
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[ranks[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[ranks[tp[i]]]--]=tp[i];
    }
    
    void get_height(){ 
        int k=0; //求height[]
        for(int i=1;i<=n;i++){
            if(k) k--; int j=sa[ranks[i]-1];
            while(ss[i+k]==ss[j+k]) k++; height[ranks[i]]=k; 
        }
    }
    
    void get_sa(){
        for(int i=1;i<=n;i++) ranks[i]=ss[i]-'0'+1,tp[i]=i;
        m=127; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); for(int i=1;i<=n;i++) // swap(ranks,tp);
                b[i]=tp[i],tp[i]=ranks[i],ranks[i]=b[i]; ranks[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                ranks[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]
                    &&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        } get_height();
    }
    
    int main(){
        scanf("%d%s",&n,ss+1); get_sa();
        for(int i=2;i<=n;i++){ //按排名枚举子串(第一个舍去)  
            for(int j=height[i-1]+1;j<=height[i];j++){
                //j初始值为'排名为i-1的子串lcp + 1',j要<=当前子串lcp长度 
                int k=i; while(height[k]>=j) k++;
                //↑↑寻找lcp值>=j的子串最后一次出现的位置
                printf("%d
    ",k-i+1); //重复出现的次数
            }
        }
    }
    【p4341】外星联络 //求所有出现次数>1的子串出现的次数

    【题意】求所有出现次数>1的子串出现的次数,子串按照字典序排序。

    【分析】先求出sa和height数组,然后枚举。

    由于字符串有一个性质:字符串的所有子串就是所有后缀的所有前缀。

    可以枚举每个字符向后延续的长度。然后向右循环,看有多少个height大于该长度。

    需要注意:(1)枚举长度时要从height+1开始,因为前面的都已经处理过。

             (2)循环时不能向左循环,因为左边的已经找过。最后判断一下出现次数是否大于1即可。

    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <stack>
    #include <queue>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    /*【p4248】差异
    求(n-1)*n*(n+1)/2−(2×所有后缀的公共前缀长度lcp)。*/
    
    //【标签】后缀数组 + 数学分析 + 分情况讨论 + 单调栈维护dp
    
    //对于每一个height[i],若height[i-1]<=height[i],
    //那么height[i-1]能取到的值height[i]都能取到;
    
    //若height[i-1]>height[i],则对于i位置来说、LCP长度就是height[i]。
    
    //用单调栈维护距i最近且小于等于height[i]的位置p。
    
    //那么转移方程为:f[i]=f[p]+(i-p)*height[i],ans=∑f[i]。
    
    const int maxn=500019; int n,m; char s[maxn];
    
    int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];
    
    void qsort(){
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[rank[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    }
    
    void SuffixSort(){
        for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
        m=519; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); // swap(rank,tp); 
            for(int i=1;i<=n;i++) 
                b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]
                    &&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        }
    }
    
    void getH(){ 
        int k=0; //求height[]
        for(int i=1;i<=n;i++){
            if(k) k--; int j=sa[rank[i]-1];
            while(s[i+k]==s[j+k]) k++; height[rank[i]]=k; 
        }
    }
    
    struct node{ int val,pos; }; stack <node> sta; ll f[maxn];
    
    int main(){
        scanf("%s",s+1),n=strlen(s+1);
        SuffixSort(); getH(); ll ans=0;
        for(int i=1;i<=n;i++){ int p=0;
            while(!sta.empty()&&sta.top().val>height[i]) sta.pop();
            if(!sta.empty()) p=sta.top().pos;
            f[i]=f[p]+(i-p)*height[i],ans+=f[i];
            sta.push((node){height[i],i});
        } printf("%lld
    ",(ll)(n-1)*n*(n+1)/2-2*ans);
    }
    【p4248】差异 // 后缀数组 + 数学分析 + 分情况讨论 + 单调栈维护dp

    【题意】求(n-1)*n*(n+1)/2−(2×所有后缀的公共前缀长度lcp)。

    1. 若height[i-1]<=height[i],那么height[i-1]能取到的值height[i]都能取到;
    2. 若height[i-1]>height[i],则对于i位置来说、LCP长度就是height[i]。

    用单调栈维护距i最近且 <= height[i] 的位置p。转移方程:f[i]=f[p]+(i-p)*height[i],ans=∑f[i]。


    后缀自动机

    ——时间划过风的轨迹,那个少年,还在等你

  • 相关阅读:
    调用系统相机导致照片旋转问题的修复
    JavaLearning:日期操作类
    PHP实现事件机制实例分析
    按下葫芦起了瓢
    win系统下的eclipse连接和使用linux上的hadoop集群
    利用gradle加入构建版本
    从设计到实现,一步步教你实现Android-Universal-ImageLoader-辅助类
    I帧、P帧和B帧的特点
    tcp_tw_recycle检查tcp_timestamps的内核代码
    OBS源码编译开发
  • 原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10574154.html
Copyright © 2011-2022 走看看