【浮*光】#字符串# 字符串の相关练习题

 

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\n",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\n", 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\n",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] << '\n';
    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\n"); else printf("%d\n",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\n",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\n",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\n",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\n",(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]。

 


 

后缀自动机

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

posted @ 2019-03-21 20:04  花神&缘浅flora  阅读(210)  评论(0编辑  收藏  举报