【模板整合计划】字符串
【模板整合计划】字符串
一:【字符串 Hash】
【模板】 兔子与兔子 \(\text{[CH1401]}\)
#include<iostream>
#include<string>
#include<cstdio>
#define LL long long
using namespace std;
const int N=1e6+5;
int l1,r1,l2,r2,n,m,i,p=27;
LL f[N],mi[N];string a;
inline LL Hash(int l,int r){return f[r]-f[l-1]*mi[r-l+1];}
int main(){
cin>>a;n=a.size();a='$'+a;mi[0]=1;
for(i=1;i<=n;i++)f[i]=f[i-1]*p+a[i]-'a'+1,mi[i]=mi[i-1]*p;
scanf("%d",&m);
while(m--){
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if(Hash(l1,r1)==Hash(l2,r2))printf("Yes\n");
else printf("No\n");
}
}
二:【最小表示法】
【模板】 最小表示法 / 工艺 \(\text{[P1368]}\)
#include<cstdio>
int i,j,k,n,st,s[600003];
inline void in(int &x){
char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
inline void out(int x){if(x>9)out(x/10);putchar(x%10+'0');}
int main(){
in(n);
for(i=1;i<=n;s[i+n]=s[i],++i)in(s[i]);
i=1,j=2;
while(i<=n&&j<=n){
k=0;
while(k<n&&s[i+k]==s[j+k])++k;
if(k==n)break;
if(s[i+k]>s[j+k]){i+=k+1;if(i==j)++i;}
else{j+=k+1;if(i==j)++j;}
}
st=i>j?j:i;
for(i=st;i<st+n;++i)out(s[i]),putchar(' ');
}
三:【马拉车 (Manaher)】
【模板】 \(\text{Manacher}\) 算法 \(\text{[P3805]}\)
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=22000003;
char a[N]={'$','|'},c=getchar();int r[N],mid,ans,R,n=1,i;
int main(){
while(c>='a'&&c<='z')a[++n]=c,a[++n]='|',c=getchar();
for(i=1;i<=n;i++){
if(i<=R)r[i]=min(r[(mid<<1)-i],R-i+1);
else r[i]=1;
while(a[i-r[i]]==a[i+r[i]])r[i]++;
if(i+r[i]-1>R)mid=i,R=i+r[i]-1;
ans=max(ans,r[i]-1);
}
printf("%d",ans);
}
四:【Trie】
1.【标准版 Trie 树】
【模板】 于是他错误的点名开始了 \(\text{[P2580]}\)
#pragma once
#include<cstring>
#include<cstdio>
#define R register int
using namespace std;
const int SP=500003;
int trie[SP][26],pan[SP],ed[SP],re,t=1,n;
char ch[55];
inline void add(char ch[]){
R a,i=0,p=1,m=strlen(ch);
for(;i<m;i++){
a=ch[i]-'a';
if(!trie[p][a])trie[p][a]=++t;
p=trie[p][a];
}
ed[p]=1;
}
inline int find(char ch[]){
R re,i=0,p=1,m=strlen(ch);
for(;i<m;i++){
p=trie[p][ch[i]-'a'];
if(!p)return 0;
}
re=ed[p];
if(ed[p])ed[p]++;
return re;
}
int main(){
scanf("%d",&n);
while(n--){scanf("%s",ch);add(ch);}
scanf("%d",&n);
while(n--){
scanf("%s",ch);
re=find(ch);
if(!re)printf("WRONG\n");
else if(re==1)printf("OK\n");
else printf("REPEAT\n");
}
}
2.【压缩版 Trie 树】
听教练 \(ysf\) 说有这么个东西,用来压空间效果比较好,\(\text{YudeS}\) 楪 貌似学了,但我还不会。
3.【可持久化 Trie 树】
(这个应该是属于高阶数据结构吧)
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define Re register int
using namespace std;
const int N=6e5+5,logN=24;
int n,x,y,z,T,A[N],pt[N];char op;
inline void in(Re &x){
Re fu=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9')fu|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=fu?-x:x;
}
struct Trie{
int O,cnt[N*(logN+2)],tr[N*(logN+2)][2];
inline void insert(Re p,Re q,Re o,Re x){
if(o<0)return;Re i=(x>>o)&1;
tr[p][i]=++O,tr[p][i^1]=tr[q][i^1];
cnt[tr[p][i]]=cnt[tr[q][i]]+1;
insert(tr[p][i],tr[q][i],o-1,x);
}
inline int ask(Re p,Re q,Re o,Re x){
if(o<0)return 0;Re i=(x>>o)&1;
if(cnt[tr[q][i^1]]>cnt[tr[p][i^1]])return (1<<o)+ask(tr[p][i^1],tr[q][i^1],o-1,x);
else return ask(tr[p][i],tr[q][i],o-1,x);
}
}T1;
int main(){
// freopen("123.txt","r",stdin);
in(n),in(T);
for(Re i=1;i<=n;++i)in(A[i]),A[i]^=A[i-1],T1.insert(pt[i]=++T1.O,pt[i-1],logN,A[i]);
T1.insert(pt[0]=++T1.O,0,logN,0);
while(T--){
scanf(" %c",&op),in(x);
if(op=='A')++n,A[n]=A[n-1]^x,T1.insert(pt[n]=++T1.O,pt[n-1],logN,A[n]);
else in(y),in(z),--x,--y,printf("%d\n",T1.ask(x?pt[x-1]:0,pt[y],logN,z^A[n]));
}
}
五:【KMP (Knuth-Morris-Pratt)】
1.【标准版 KMP】
【模板】 \(\text{KMP}\) 字符串匹配 \(\text{[P3375]}\)
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
int i,j,n,m,next[1000005];string a,b;
int main(){
cin>>a>>b;
n=a.size();a='$'+a;
m=b.size();b='$'+b;
for(i=2,j=0;i<=m;i++){
while(j&&b[i]!=b[j+1])j=next[j];
if(b[i]==b[j+1])j++;
next[i]=j;
}
for(i=1,j=0;i<=n;i++){
while(j&&a[i]!=b[j+1])j=next[j];
if(a[i]==b[j+1])j++;
if(j==m){j=next[j];printf("%d\n",i-m+1);}
}
for(i=1;i<=m;i++)printf("%d ",next[i]);
}
2.【扩展 KMP (EXKMP) / Z 函数】
【模板】 扩展 \(\text{KMP}\) \(\text{[P5410]}\)
还不会,先咕着。
六:【自动机与后缀三兄弟】
前方大量英文单词来袭,请做好准备Σ(⊙▽⊙"a)
1.【AC 自动机 (Aho-Corasick Automaton)】
【模板】 \(\text{AC}\) 自动机(二次加强版)\(\text{[P5357]}\)
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define Re register int
#define LL long long
using namespace std;
const int N=2e5+3;
int n;char ch[N*10];
inline void in(Re &x){
int fu=0;x=0;char c=getchar();
while(c<'0'||c>'9')fu|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=fu?-x:x;
}
struct AC_Automaton{
int O,edp[N],fail[N],tr[N][26];queue<int>Q;
AC_Automaton(){O=1;}
inline void insert(char ch[],Re id){
Re p=1;
for(Re i=1;ch[i];++i){
Re a=ch[i]-'a';
if(!tr[p][a])tr[p][a]=++O;
p=tr[p][a];
}
edp[id]=p;
}
inline void get_fail(){
for(Re i=0;i<26;++i)tr[0][i]=1;
Q.push(1);
while(!Q.empty()){
Re x=Q.front();Q.pop();
for(Re i=0;i<26;++i)
if(tr[x][i])fail[tr[x][i]]=tr[fail[x]][i],Q.push(tr[x][i]);
else tr[x][i]=tr[fail[x]][i];
}
}
int ru[N],cnt[N];
inline void sakura(char ch[]){
for(Re i=1,p=1;ch[i];++i)p=tr[p][ch[i]-'a'],++cnt[p];
for(Re i=2;i<=O;++i)++ru[fail[i]];
for(Re i=1;i<=O;++i)if(!ru[i])Q.push(i);
while(!Q.empty()){
Re x=Q.front();Q.pop();
cnt[fail[x]]+=cnt[x];
if(!(--ru[fail[x]]))Q.push(fail[x]);
}
for(Re i=1;i<=n;++i)printf("%d\n",cnt[edp[i]]);
}
}AC;
int main(){
// freopen("123.txt","r",stdin);
in(n);
for(Re i=1;i<=n;++i)scanf("%s",ch+1),AC.insert(ch,i);
AC.get_fail();
scanf("%s",ch+1);AC.sakura(ch);
}
2.【回文自动机 / 回文树 / PAM (Palindromic Automaton)】
#include<algorithm>
#include<cstring>
#include<cstdio>
#define Re register int
using namespace std;
const int N=5e5+5;
char s[N];
inline void in(Re &x){
Re f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
struct Palindromic_Automaton{
int O,last,siz[N],cnt[N],len[N],fail[N],trans[N][26];
Palindromic_Automaton(){O=1,len[1]=-1,fail[0]=fail[1]=last=1;}
//0:偶数串的根节点
//1:奇数串的根节点
//siz[x]:状态x出现的次数
//cnt[x]:状态x所代表的回文串的回文后缀个数
inline int find(Re p,Re id){while(s[id-1-len[p]]!=s[id])p=fail[p];return p;}
inline void insert(Re ch,Re id){
Re cur=find(last,id),now=trans[cur][ch];
if(!now){
now=++O,len[now]=len[cur]+2;
fail[now]=trans[find(fail[cur],id)][ch];
cnt[now]=cnt[fail[now]]+1;
trans[cur][ch]=now;
}
++siz[now],last=now;
}
inline void sakura(){
for(Re i=O;i>=2;--i)siz[fail[i]]+=siz[i];
}
}PAM;
int main(){
// freopen("123.txt","r",stdin);
scanf("%s",s+1);
for(Re i=1,k=0;s[i];++i){
s[i]=(s[i]-'a'+k)%26+'a';
PAM.insert(s[i]-'a',i);
printf("%d ",k=PAM.cnt[PAM.last]);
}
}
3.【子序列自动机 】
【模板】 子序列自动机 \(\text{[P5826]}\)
还不会,先咕着。
4.【后缀数组 / SA (Suffix Array)】
(1).【倍增后缀数组算法 (Manber-Mayer Suffix Array)】
#include<algorithm>
#include<cstring>
#include<cstdio>
#define Re register int
#define LL long long
using namespace std;
const int N=1e6+3;
inline void in(Re &x){
int fu=0;x=0;char c=getchar();
while(c<'0'||c>'9')fu|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=fu?-x:x;
}
struct Suffix_Array{
int n,m,x[N],y[N],H[N],SA[N],cnt[N],rank[N],Height[N];char s[N];
//Suf[i]: s[i,n]
//len: 已经求出了长度为len的串的排名
//x[i]: Suf[i]的排名 (排序第一关键字) (上一次排序后得到的rank[i])
//y[i]: 按第len个字符排出来的顺序(排序第二关键字) (第二关键字排名为i的数的位置)
//H[i]: H[i]=Height[rank[i]] 即 LCP(Suf[i],Suf[SA[rank[i]-1]])
//SA[i]: 排名为i的后缀第一个字符所在位置
//cnt[i]: 第一关键字排名为 i(1~i)的数有多少个
//rank[i]: Suf[i]的排名
//Height[i]: Height[i]=H[SA[i]] 即 LCP(Suf[SA[i]],Suf[SA[i-1]])
inline void get_SA(){
m=128;
for(Re i=1;i<=n;++i)++cnt[x[i]=s[i]];
for(Re i=2;i<=m;++i)cnt[i]+=cnt[i-1];
for(Re i=n;i>=1;--i)SA[cnt[x[i]]--]=i;
for(Re len=1;len<=n;len<<=1){
Re t=0;
for(Re i=n-len+1;i<=n;++i)y[++t]=i;
for(Re i=1;i<=n;++i)if(SA[i]>len)y[++t]=SA[i]-len;
for(Re i=1;i<=m;++i)cnt[i]=0;
for(Re i=1;i<=n;++i)++cnt[x[i]];
for(Re i=2;i<=m;++i)cnt[i]+=cnt[i-1];
for(Re i=n;i>=1;--i)SA[cnt[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
x[SA[1]]=m=1;//把旧的x存进了y并清零x
for(Re i=2;i<=n;++i)
x[SA[i]]=(y[SA[i]]==y[SA[i-1]]&&y[SA[i]+len]==y[SA[i-1]+len])?m:++m;//注意这里的SA[i]+len和SA[i-1]+len有可能访问到n+1,所以上面的N最好稍开大一点,不要尝试卡这点小常数空间
if(m==n)break;
}
for(Re i=1;i<=n;++i)printf("%d ",SA[i]);
}
inline void get_Height(){
for(Re i=1;i<=n;++i)rank[SA[i]]=i;
for(Re i=1,k=0;i<=n;++i){
if(rank[i]==1)continue;
if(k)--k;//H[i-1]-1<=H[i]
Re j=SA[rank[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])++k;
Height[rank[i]]=k;
}
// for(Re i=1;i<=n;++i)printf("%d ",Height[i]);
}
}SA;
int main(){
// freopen("123.txt","r",stdin);
scanf("%s",SA.s+1),SA.n=strlen(SA.s+1);
SA.get_SA(),SA.get_Height();
}
(2).【SA-IS (Suffix-Array-Induce-Sort)】
还不会,先咕着。
(3).【DC3 (Difference Cover modulo 3)】
还不会,先咕着。
(4).【动态后缀数组 / 后缀平衡树】
还不会,先咕着。
(5).【SAM 转后缀树转后缀数组】
【模板】 不同子串个数 \(\text{[P2408]}\)
#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=1e5+5;
int n;char s[N];LL ans;
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
struct Suffix_Array{
int H[N],SA[N],rank[N],height[N];
inline void get_height(){
for(Re i=1;i<=n;++i)rank[SA[i]]=i;
for(Re i=1,k=0;i<=n;++i){
if(rank[i]==1)continue;
if(k)--k;
Re j=SA[rank[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])++k;
height[rank[i]]=H[i]=k;
}
}
}SA;
int pan[N<<1],pos[N<<1];
struct Suffix_Tree{
int t,tr[N<<1][26];
inline void add(Re x,Re y,Re c){tr[x][c]=y;}
inline void dfs(Re x){
if(pan[x])SA.SA[++t]=pos[x];
for(Re i=0;i<26;++i)if(tr[x][i])dfs(tr[x][i]);
}
}ST;
struct Suffix_Automaton{
int O,last,link[N<<1],maxlen[N<<1],trans[N<<1][26];
Suffix_Automaton(){last=O=1;}
inline void insert(Re ch,Re id){
Re z=++O,p=last;maxlen[z]=maxlen[p]+1,pan[z]=1,pos[z]=id;
while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
if(!p)link[z]=1;
else{
Re x=trans[p][ch];
if(maxlen[p]+1==maxlen[x])link[z]=x;
else{
Re y=++O;maxlen[y]=maxlen[p]+1;
for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
pos[y]=pos[x],link[y]=link[x],link[x]=link[z]=y;//注意pos[y]=pos[x]
}
}
last=z;
}
inline void build(){
for(Re i=n;i>=1;--i)insert(s[i]-'a',i);
for(Re i=2;i<=O;++i)ST.add(link[i],i,s[pos[i]+maxlen[link[i]]]-'a');//转移边上的ch为子节点的首字母
}
}SAM;
int main(){
// freopen("123.txt","r",stdin);
in(n),scanf("%s",s+1),SAM.build(),ST.dfs(1),SA.get_height();
for(Re i=1;i<=n;++i)ans+=n-SA.SA[i]+1-SA.height[i];
printf("%lld\n",ans);
}
5.【后缀自动机 / SAM (Suffix Automaton)】
(1).【标准版 SAM】
【模板】 后缀自动机(\(\text{SAM}\))\(\text{[P3804]}\)
#include<algorithm>
#include<cstdio>
#include<queue>
#define Re register int
#define LL long long
using namespace std;
const int N=2e6+5;
char ch[2000003];
inline void in(Re &x){
int fu=0;x=0;char c=getchar();
while(c<'0'||c>'9')fu|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=fu?-x:x;
}
struct Suffix_Automaton{
int O,last,ru[N],siz[N],link[N],maxlen[N],minlen[N],trans[N][26];queue<int>Q;
//siz:一个等价类节点endpos大小(即出现次数)
Suffix_Automaton(){last=O=1;}//起点为 1
inline void insert(char ch){
Re z=++O,p=last;siz[z]=1,maxlen[z]=maxlen[last]+1;//len为主链长
while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];//没有trans[...][ch]的一段
if(!p)link[z]=1;//没找到trans[...][ch]
else{
Re x=trans[p][ch];
if(maxlen[p]+1==maxlen[x])link[z]=x;//直接接上去
else{//拆点
Re y=++O;maxlen[y]=maxlen[p]+1;//把原x拆成小于等于maxlen[p]+1的一段(即y)和大于maxlen[p]+1的一段(即新x)
for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];//复制原x的trans
while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];//把指向了原x的那一段都指向y
link[y]=link[x];//复制原x的link
link[z]=link[x]=y;
}
}
last=z;
}
inline void Tuopu(){//在正向link构成的DAG上跑拓扑求siz
for(Re i=2;i<=O;++i)++ru[link[i]];
for(Re i=1;i<=O;++i)if(!ru[i])Q.push(i);
while(!Q.empty()){
Re x=Q.front();Q.pop();
siz[link[x]]+=siz[x];
if(!(--ru[link[x]]))Q.push(link[x]);
}
}
inline void sakura(){
for(Re i=2;i<=O;++i)minlen[i]=maxlen[link[i]]+1;
Tuopu();Re ans=0;
for(Re i=1;i<=O;++i)if(siz[i]>1)ans=max(ans,siz[i]*maxlen[i]);
printf("%d\n",ans);
}
}SAM;
int main(){
// freopen("123.txt","r",stdin);
scanf("%s",ch+1);
for(Re i=1;ch[i];++i)SAM.insert(ch[i]-'a');
SAM.sakura();
}
(2).【LCT 动态维护 SAM】
【模板】 \(\text{SubString [Bzoj2555]}\)
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<string>
#include<queue>
#define LL long long
#define Re register int
using namespace std;
const int N=12e5+3;
int T,ans,mask;char op[10],s[N];
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
inline string In(Re mask){
scanf("%s",s);string chars=s;
for(int j=0;j<chars.length();j++){
mask=(mask*131+j)%chars.length();
char t=chars[j];
chars[j]=chars[mask];
chars[mask]=t;
}
return chars;
}
struct Suffix_Automaton{
struct Link_Cut_Tree{
#define pl tr[p].ps[0]
#define pr tr[p].ps[1]
#define pf tr[p].fa
int Q[N];
struct QAQ{int fa,ans,add,ps[2];}tr[N];
inline void add(Re p,Re v){tr[p].ans+=v,tr[p].add+=v;}
inline void pushdown(Re p){
Re v=tr[p].add;
if(v){
if(pl)add(pl,v);
if(pr)add(pr,v);
tr[p].add=0;
}
}
inline int nort(Re p){return tr[pf].ps[0]==p||tr[pf].ps[1]==p;}
inline int which(Re p){return tr[pf].ps[1]==p;}
inline void connect(Re p,Re fa,Re o){tr[pf=fa].ps[o]=p;}
inline void rotate(Re p){
Re fa=pf,fas=which(p);
Re pa=tr[fa].fa,pas=which(fa);
Re x=tr[p].ps[fas^1];
if(nort(fa))tr[pa].ps[pas]=p;pf=pa;
connect(x,fa,fas),connect(fa,p,fas^1);
}
inline void splay(Re p){
Re x=p,t=0;Q[++t]=x;
while(nort(x))Q[++t]=x=tr[x].fa;
while(t)pushdown(Q[t--]);
for(Re fa;nort(p);rotate(p))
if(nort(fa=pf))rotate(which(p)==which(fa)?fa:p);
}
inline void access(Re p){for(Re son=0;p;son=p,p=pf)splay(p),pr=son;}
inline void link(Re x,Re y){tr[x].fa=y,access(y),splay(y),add(y,tr[x].ans);}
inline void cut(Re x){
access(x),splay(x),add(tr[x].ps[0],-tr[x].ans);
tr[tr[x].ps[0]].fa=0,tr[x].ps[0]=0;
}
inline int ask(Re x){splay(x);return tr[x].ans;}
}LCT;
int O,last,link[N],maxlen[N],trans[N][30];
Suffix_Automaton(){last=O=1;}
inline void insert(char ch){
Re z=++O,p=last;LCT.tr[z].ans=1,maxlen[z]=maxlen[last]+1;
while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
if(!p)link[z]=1,LCT.link(z,1);
else{
Re x=trans[p][ch];
if(maxlen[x]==maxlen[p]+1)link[z]=x,LCT.link(z,x);
else{
Re y=++O;maxlen[y]=maxlen[p]+1;
for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
link[y]=link[x],LCT.cut(x),LCT.link(y,link[y]);
link[x]=link[z]=y,LCT.link(x,y),LCT.link(z,y);
}
}
last=z;
}
inline void add(string ch){
for(Re i=0;ch[i];++i)insert(ch[i]-'A');
}
inline int ask(string ch){
Re p=1;
for(Re i=0;ch[i];++i)if(!(p=trans[p][ch[i]-'A']))return 0;
return LCT.ask(p);
}
}SAM;
int main(){
// freopen("123.txt","r",stdin);
in(T),scanf("%s",s+1);
for(Re i=1;s[i];++i)SAM.insert(s[i]-'A');
while(T--){
scanf("%s",op);string s=In(mask);
if(op[0]=='A')SAM.add(s);
else mask^=(ans=SAM.ask(s)),printf("%d\n",ans);
}
}
(3).【广义 SAM(在线构造)】
【模板】 广义后缀自动机(广义 \(\text{SAM}\))\(\text{[P6139]}\)
#include<algorithm>
#include<cstdio>
#include<queue>
#define Re register int
#define LL long long
using namespace std;
const int N=2e6+5;
int n;char ch[N];
inline void in(Re &x){
int fu=0;x=0;char c=getchar();
while(c<'0'||c>'9')fu|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=fu?-x:x;
}
struct Suffix_Automaton{
int O,link[N],maxlen[N],trans[N][26];
//link[i]: 后缀链接
//trans[i]: 状态转移数组
Suffix_Automaton(){O=1;}//根初始化为1
inline int insert(Re ch,Re last){
if(trans[last][ch]){
Re p=last,x=trans[p][ch];
if(maxlen[p]+1==maxlen[x])return x;//即最初的特判1
else{
Re y=++O;maxlen[y]=maxlen[p]+1;
for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
link[y]=link[x],link[x]=y;
return y;//即最初的特判2
}
}
Re z=++O,p=last;maxlen[z]=maxlen[last]+1;
while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
if(!p)link[z]=1;
else{
Re x=trans[p][ch];
if(maxlen[p]+1==maxlen[x])link[z]=x;
else{
Re y=++O;maxlen[y]=maxlen[p]+1;
for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
link[y]=link[x],link[z]=link[x]=y;
}
}
return z;
}
inline void sakura(){
LL ans=0;
for(Re i=2;i<=O;++i)ans+=maxlen[i]-maxlen[link[i]];
printf("%lld\n",ans);
}
}SAM;
int main(){
// freopen("123.txt","r",stdin);
in(n);
for(Re i=1;i<=n;++i){
scanf("%s",ch+1);Re last=1;
for(Re j=1;ch[j];++j)last=SAM.insert(ch[j]-'a',last);
}
SAM.sakura();
}
(4).【广义 SAM(离线构造)】
【模板】 广义后缀自动机(广义 \(\text{SAM}\))\(\text{[P6139]}\)
#include<algorithm>
#include<cstdio>
#include<queue>
#define Re register int
#define LL long long
using namespace std;
const int N=2e6+5,M=1e6+3;
int n,t;char ch[N];
inline void in(Re &x){
int fu=0;x=0;char c=getchar();
while(c<'0'||c>'9')fu|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=fu?-x:x;
}
struct Trie{
int O,c[M],fa[M],tr[M][26];
//fa[x]: Trie树上x的父节点
//c[x]: Trie树上x的颜色
Trie(){O=1;}//根初始化为1
inline void insert(char ch[]){
Re p=1;
for(Re i=1;ch[i];++i){
Re a=ch[i]-'a';
if(!tr[p][a])tr[p][a]=++O,fa[O]=p,c[O]=a;
p=tr[p][a];
}
}
}T1;
struct Suffix_Automaton{
int O,pos[N],link[N],maxlen[N],trans[N][26];queue<int>Q;
//pos[x]:Trie上的x节点(路径1->x所表示的字符串)在SAM上的对应节点编号
//link[i]: 后缀链接
//trans[i]: 状态转移数组
Suffix_Automaton(){O=1;}//根初始化为1
inline int insert(Re ch,Re last){//和普通SAM一样
Re x,y,z=++O,p=last;maxlen[z]=maxlen[last]+1;
while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
if(!p)link[z]=1;
else{
x=trans[p][ch];
if(maxlen[p]+1==maxlen[x])link[z]=x;
else{
y=++O;maxlen[y]=maxlen[p]+1;
for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
link[y]=link[x],link[z]=link[x]=y;
}
}
return z;
}
inline void build(){//bfs遍历Trie树构造广义SAM
for(Re i=0;i<26;++i)if(T1.tr[1][i])Q.push(T1.tr[1][i]);//插入第一层字符
pos[1]=1;//Tire树上的根1在SAM上的位置为根1
while(!Q.empty()){
Re x=Q.front();Q.pop();
pos[x]=insert(T1.c[x],pos[T1.fa[x]]);//注意是pos[Trie->fa[x]]
for(Re i=0;i<26;++i)if(T1.tr[x][i])Q.push(T1.tr[x][i]);
}
}
inline void sakura(){
LL ans=0;
for(Re i=2;i<=O;++i)ans+=maxlen[i]-maxlen[link[i]];
printf("%lld\n",ans);
}
}SAM;
int main(){
// freopen("123.txt","r",stdin);
in(n);
for(Re i=1;i<=n;++i)scanf("%s",ch+1),T1.insert(ch);
SAM.build(),SAM.sakura();
}
(5).【线段树合并维护广义 SAM 的 siz(等价类出现次数)】
【模板】 \(\text{Forensic Examination}\) \(\text{[CF666E]}\)
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#define LL long long
#define Re register int
using namespace std;
const int N=5e5+3,M=5e4+3,logN=21;
int n,m,x,y,l,r,T,pos[N];char s[N],ch[M];
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
struct QWQ{
int x,id;QWQ(Re X=0,Re ID=0){x=X,id=ID;}
inline bool operator>(const QWQ &O)const{return x!=O.x?x>O.x:id<O.id;}
};
inline QWQ max(QWQ A,QWQ B){return A>B?A:B;}
int pt[N+M<<1];
struct Segment_Tree{
#define pl (tr[p].lp)
#define pr (tr[p].rp)
#define mid ((L+R)>>1)
int O;
struct QAQ{int lp,rp;QWQ ans;}tr[(M<<1)*30];
inline void pushup(Re p){
tr[p].ans=max(tr[pl].ans,tr[pr].ans);
}
inline void change(Re &p,Re L,Re R,Re x){
if(!p)p=++O;
if(L==R){++tr[p].ans.x,tr[p].ans.id=L;return;}
if(x<=mid)change(pl,L,mid,x);
else change(pr,mid+1,R,x);
pushup(p);
}
inline int merge(Re p,Re q,Re L,Re R){
if(!p||!q)return p+q;
Re x=++O;
if(L==R){tr[x]=tr[p],tr[x].ans.x+=tr[q].ans.x;return x;}
tr[x].lp=merge(pl,tr[q].lp,L,mid);
tr[x].rp=merge(pr,tr[q].rp,mid+1,R);
pushup(x);return x;
}
inline QWQ ask(Re p,Re L,Re R,Re l,Re r){
if(!p)return QWQ(0,m+1);
if(l<=L&&R<=r)return tr[p].ans;
QWQ ans=QWQ(0,m+1);
if(l<=mid)ans=max(ans,ask(pl,L,mid,l,r));
if(r>mid)ans=max(ans,ask(pr,mid+1,R,l,r));
return ans;
}
}TR;
struct Suffix_Automaton{
int O,link[N+M<<1],maxlen[N+M<<1],trans[N+M<<1][26];
Suffix_Automaton(){O=1;}
inline int insert(Re ch,Re last,Re id){
if(trans[last][ch]){
Re p=last,x=trans[p][ch];
if(maxlen[p]+1==maxlen[x]){if(id)TR.change(pt[x],1,m,id);return x;}
else{
Re y=++O;maxlen[y]=maxlen[p]+1;
for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
link[y]=link[x],link[x]=y;
if(id)TR.change(pt[y],1,m,id);
return y;
}
}
Re z=++O,p=last;maxlen[z]=maxlen[p]+1;
while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
if(!p)link[z]=1;
else{
Re x=trans[p][ch];
if(maxlen[x]==maxlen[p]+1)link[z]=x;
else{
Re y=++O;maxlen[y]=maxlen[p]+1;
for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
link[y]=link[x],link[x]=link[z]=y;
}
}
if(id)TR.change(pt[z],1,m,id);
return z;
}
int o,deep[N+M<<1],head[N+M<<1],ant[N+M<<1][23];
struct QAQ{int to,next;}a[N+M<<1];
inline void add(Re x,Re y){a[++o].to=y,a[o].next=head[x],head[x]=o;}
inline void dfs(Re x,Re fa){
deep[x]=deep[ant[x][0]=fa]+1;
for(Re i=1;(1<<i)<=deep[x];++i)ant[x][i]=ant[ant[x][i-1]][i-1];
for(Re i=head[x],to;i;i=a[i].next)
dfs(to=a[i].to,x),pt[x]=TR.merge(pt[x],pt[to],1,m);
}
inline void build(){
for(Re i=2;i<=O;++i)add(link[i],i);dfs(1,0);
}
inline int get(Re x,Re len){
Re p=pos[x];
for(Re i=logN;i>=0;--i)if(ant[p][i]&&maxlen[ant[p][i]]>=len)p=ant[p][i];
return p;
}
inline void sakura(Re l,Re r,Re x,Re y){
QWQ ans=TR.ask(pt[get(y,y-x+1)],1,m,l,r);
if(ans.x==0)ans.id=l;
printf("%d %d\n",ans.id,ans.x);
}
}SAM;
int main(){
// freopen("123.txt","r",stdin);
scanf("%s",s+1),n=strlen(s+1),in(m);
for(Re i=1;i<=m;++i){
scanf("%s",ch+1);Re last=1;
for(Re j=1;ch[j];++j)last=SAM.insert(ch[j]-'a',last,i);
}
for(Re i=1,last=1;i<=n;++i)pos[i]=last=SAM.insert(s[i]-'a',last,0);
SAM.build(),in(T);
while(T--)in(l),in(r),in(x),in(y),SAM.sakura(l,r,x,y);
}
6.【后缀树 / ST (Suffix Tree)】
(1).【Ukk算法 (Ukkonen)】
(2).【SAM 转后缀树】
#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=4e5+10;
int n,x,y,T;LL P,M,G,K;char s[N>>1];
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
struct Sakura2{
int O,last,pos[N],siz[N],link[N],maxlen[N],trans[N][26];
//siz[x]: 节点x的出现次数 (endpos的大小)
//link[x]: x在parent树上的父亲
//pos[x]: 节点x某一次出现位置 (用于构建后缀树中的压缩边link[x]->x),
///翻转后的s中 倒序遍历pos[x]-maxlen[link[x]] -> pos[x]-maxlen[x]+1得到的字符串 即为后缀树中的压缩边 link[x]->x
inline void insert(Re ch,Re id){//SAM新建节点
Re z=++O,p=last;pos[z]=id,siz[z]=1,maxlen[z]=maxlen[last]+1;
while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
if(!p)link[z]=1;
else{
Re x=trans[p][ch];
if(maxlen[p]+1==maxlen[x])link[z]=x;
else{
Re y=++O;maxlen[y]=maxlen[p]+1,pos[y]=pos[x];
for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
link[y]=link[x],link[z]=link[x]=y;
}
}
last=z;
}
int t,ID[N],to[N][26];LL S[N];
//to[x][ch]: 后缀树的trans数组
//ID[x]: 顺序遍历后缀树的第x个节点编号
//S[x]: 顺序遍历后缀树的前x个节点总共代表了多少个字符
inline void dfs1(Re x){//遍历 SAM的parent树\后缀树 获取siz
for(Re i=0;i<26;++i)
if(to[x][i])dfs1(to[x][i]),siz[x]+=siz[to[x][i]];
}
inline void dfs2(Re x){//遍历 SAM的parent树\后缀树 获取节点顺序
if(x!=1)ID[++t]=x;//没有储存信息的根节点1就不要了
for(Re i=0;i<26;++i)if(to[x][i])dfs2(to[x][i]);
}
inline LL calc(Re L,Re R){return 1ll*(L+R)*(R-L+1)/2;}//计算从L加到R的等差数列
inline void build(){
last=O=1;//根节点设为1
for(Re i=1;i<=n/2;++i)swap(s[i],s[n-i+1]);//翻转原字符串
for(Re i=1;i<=n;++i)insert(s[i]-'a',i);
for(Re i=2;i<=O;++i)to[link[i]][s[pos[i]-maxlen[link[i]]]-'a']=i;//构建后缀树。
//由于pos[x]是endpos[x]中的任意一个,所以获取边link[x]->x压缩掉的字符串信息时只能用pos[x],不能用pos[link[x]]
dfs1(1),dfs2(1);//遍历 SAM的parent树\后缀树
for(Re i=1;i<=t;++i)x=ID[i],S[i]=S[i-1]+1ll*siz[x]*calc(maxlen[link[x]]+1,maxlen[x]);
}
inline char ask(LL K){
Re l=1,r=t,x;
while(l<r){//二分找到第一个大于等于K的位置
Re mid=l+r>>1;
if(S[mid]<K)l=mid+1;
else r=mid;
}
x=ID[l],K-=S[l-1],l=maxlen[link[x]]+1,r=maxlen[x];
while(l<r){//二分找到第一个大于等于K的位置
Re mid=l+r>>1;
if(1ll*siz[x]*calc(maxlen[link[x]]+1,mid)<K)l=mid+1;
else r=mid;
}
K-=1ll*siz[x]*calc(maxlen[link[x]]+1,l-1),K=(K-1)%l+1;//注意取模的方式
return s[pos[x]-K+1];//注意方向:往左数第K位
}
char ans;
inline void sakura(){
build();
while(T--)scanf("%lld%lld",&P,&M),K=P*G%M+1,G+=(ans=ask(K)),putchar(ans),puts("");
}
}T2;
int main(){
freopen("letter.in","r",stdin);
freopen("letter.out","w",stdout);
scanf("%s",s+1),n=strlen(s+1);in(T);
T2.sakura();
fclose(stdin);
fclose(stdout);
}