【浮*光】#字符串# 字符串の相关练习题
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); } }
#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); } }
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]<<" "; }
#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); //重复出现的次数 } } }
这题也可以用 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); }
#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); } }
【不可重叠最长重复子串】 二分答案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次的可重叠的最长子串 }
【题意】给定一个序列,求序列中至少出现 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); } }
【题意】给定一个字符串,求不相同的子串的个数。
【分析】只考虑后缀的前缀,则排第 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; }
【题意】把需要加密的信息排成一圈,读出最后一列字符作为加密后的字符串。
【分析】把字符串接到自己后面长度变成为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; }
通过一个无用字符拼接两个字符串,求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); //重复出现的次数 } } }
【题意】求所有出现次数>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); }
【题意】求(n-1)*n*(n+1)/2−(2×所有后缀的公共前缀长度lcp)。
- 若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]。
后缀自动机
——时间划过风的轨迹,那个少年,还在等你