.
都是些bzoj原题辣,这几天刚做的
Bzoj4032
有趣的dp题,也要用到各种自动机
注意到题目的两个关键词“子串”和“子序列”
考虑对A和B串建立后缀自动机和序列自动机
序列自动机:可以识别一个序列所有子序列的自动机
想必学过自动机的各位都知道这个玩意怎么建,这里不再阐述
让后我们考虑这些询问
询问1:直接用SAM做类似LCS的做法求出A每个前缀和B匹配的最长距离,取最小+1
询问2:枚举A串所有子串并在B的序列自动机上面跑记录最小答案
询问3:遍历B的SAM,对应在A的序列自动机上一起走,如果走到一个节点,A的转移有B以外的转移,那么返回该节点的深度,否则继续dfs
询问4:直接上bfs,记一个三维向量 ( x , y , d ) (x,y,d) (x,y,d)表示走到串A的第x个,串B的第y个,已经取出的长度为d,转移的时候枚举下一个匹配的字符即可
还是比较好理解的~
#include
#include
#include
#include
#define N 4010
using namespace std;
struct V{ int x,y,d; } z;
char A[N],B[N]; int n,m,l[N],f[N][N]; queue<V> q;
namespace SAM{
int s[N][26],mx[N],f[N],cnt=1,lst=1;
inline int extend(int c){
int p=lst,np=lst=++cnt,q,nq;
mx[np]=mx[p]+1;
for(;p&&!s[p][c];p=f[p]) s[p][c]=np;
if(!p) return f[np]=1;
q=s[p][c];
if(mx[q]==mx[p]+1) f[np]=q;
else{
nq=++cnt;
mx[nq]=mx[p]+1;
f[nq]=f[q]; f[q]=f[np]=nq;
memcpy(s[nq],s[q],26<<2);
for(;p&&s[p][c]==q;p=f[p]) s[p][c]=nq;
}
}
inline void match(){
for(int x=1,i=1,c=0;i<=n;++i){
for(;x>1 && !s[x][A[i]-'a'];x=f[x]);
c=mx[x];
if(!s[x][A[i]-'a']) l[i]=0; else{
x=s[x][A[i]-'a']; l[i]=++c;
}
}
}
}
namespace AAM{
int s[N][26];
inline void build(){
for(int i=1;i<=n;++i)
for(int j=i-1;;--j){
s[j][A[i]-'a']=i;
if(A[j]==A[i] || !j) break;
}
}
}
namespace BAM{
int s[N][26];
inline void build(){
for(int i=1;i<=m;++i)
for(int j=i-1;;--j){
s[j][B[i]-'a']=i;
if(B[j]==B[i] || !j) break;
}
}
}
inline void dfs(int x,int y,int d){
for(int i=0;i<26;++i)
if(AAM::s[x][i]&&!SAM::s[y][i]){
*l=min(*l,d+1); return;
}
for(int i=0;i<26;++i)
if(AAM::s[x][i]&&SAM::s[y][i])
dfs(AAM::s[x][i],SAM::s[y][i],d+1);
}
int main(){
scanf("%s%s",A+1,B+1);
n=strlen(A+1); m=strlen(B+1);
for(int i=1;i<=n;++i) SAM::extend(B[i]-'a');
SAM::match();
AAM::build(); BAM::build();
*l=n; for(int i=1;i<=n;++i) if(l[i]<i) *l=min(*l,l[i]+1);
printf("%d\n",*l); *l=n;
for(int i=1;i<=n;++i){
int x=0;
for(int j=i;j<=n;++j){
x=BAM::s[x][A[j]-'a'];
if(!x){ *l=min(*l,j-i+1); break; }
}
}
printf("%d\n",*l); dfs(0,1,0);
printf("%d\n",*l); *l=n;
q.push((V){0,0,0});
for(int x,y,d;!q.empty();q.pop()){
z=q.front();
for(int i=0;i<26;++i)if(x=AAM::s[z.x][i]){
if(!(y=BAM::s[z.y][i])){ printf("%d\n",z.d+1); return 0; }
else if(!f[x][y]){ f[x][y]=1; q.push((V){x,y,z.d+1}); }
}
}
}
Bzoj2555
无比码农的一道题,为了节省代码量,牺牲效率写了ETT
大概就是用一种数据结构维护SAM的parent树,让后查询直接在SAM上面走
网上有两种做法
1.用lct实现单点查询+链修改
2.用ETT实现单点修改+子树求和
第二个因为规模*2所以效率低下,于是成功在Bzoj倒数第二(同一页上面有yww大爷和吉老师,而且碰巧代码长度是2333B)
#include
#include
#include
#define N 1200010
#define R register
#define son(x) (x==s[f[x]][1])
using namespace std;
namespace SPLAY{
int rt=0,s[N<<1][2],f[N<<1],v[N<<1],w[N<<1];
inline void ps(int x){
w[x]=w[s[x][0]]+w[s[x][1]]+v[x];
}
inline void rot(const int& x){
R int p=f[x],g=f[p],d=son(x);
s[p][d]=s[x][!d]; f[s[p][d]]=p;
if(g) s[g][son(p)]=x; f[x]=g;
s[x][!d]=p; f[p]=x; ps(x); ps(p);
}
inline void splay(const int& x,const int& r=0){
for(R int p;(p=f[x])!=r;rot(x))
if(f[p]!=r) rot(son(x)==son(p)?p:x);
if(!r) rt=x;
}
inline void adp(int x,int p){
splay(p); p+=N; splay(p,rt);
s[x][0]=s[p][0]; f[s[p][0]]=x;
s[x][1]=x+N; f[x+N]=x;
s[p][0]=x; f[x]=p; splay(x+N);
}
inline void setp(int p,int q,int nq){
splay(q); splay(p,rt);
s[q][0]=nq; f[nq]=q;
s[nq][0]=p; f[p]=nq; ps(nq); ps(q);
p+=N; q+=N; nq+=N;
splay(q); splay(p,rt);
s[q][1]=nq; f[nq]=q;
s[nq][1]=p; f[p]=nq; ps(nq); ps(q);
}
inline void init(){
f[1]=N+1; s[N+1][0]=1;
}
inline int query(int x){
splay(x); splay(x+N,rt);
return v[x]+w[s[x+N][0]];
}
}
char S[N],o[20];
int l[N],s[N][26],mx[N],sz[N],f[N];
int n,m,cnt=1,lst=1,r[N],v[N],p[N],w[N];
inline void trans(int n,int m){
for(int i=0;i<n;++i){
m=(m*131+i)%n;
swap(S[i+1],S[m+1]);
}
}
inline void extend(int c){
int p=lst,q=s[p][c],np,nq;
lst=np=++cnt; mx[np]=mx[p]+1; SPLAY::v[np]=1;
for(;p&&!s[p][c];p=f[p]) s[p][c]=np;
if(!p){ f[np]=1; SPLAY::adp(np,1); return; }
q=s[p][c];
if(mx[q]==mx[p]+1){ f[np]=q; SPLAY::adp(np,q); }
else{
nq=++cnt;
mx[nq]=mx[p]+1;
SPLAY::setp(f[q],q,nq);
SPLAY::adp(np,nq);
f[nq]=f[q]; f[q]=f[np]=nq;
memcpy(s[nq],s[q],26<<2);
for(;p&&s[p][c]==q;p=f[p]) s[p][c]=nq;
}
}
int main(){
scanf("%d%s",&n,S+1); SPLAY::init();
for(int j=1;S[j];++j) extend(S[j]-'A');
for(;n--;){
scanf("%s%s",o,S+1);
trans(strlen(S+1),m);
if(*o=='Q'){
int x=1;
for(int j=1;x&&S[j];++j) x=s[x][S[j]-'A'];
if(x) x=SPLAY::query(x); printf("%d\n",x); m^=x;
} else for(int j=1;S[j];++j) extend(S[j]-'A');
}
}
Bzoj1396
这个题我以前的Blog里有,点这里即可,不过又写了一个更加高效的做法,这里就不放code了
Bzoj3473
同Bzoj3277,这里用的是广义SAM做法
不过不得不说对这种一边建树一边往父亲遍历的做法感觉不认同(好像可以卡成 O ( n 1.5 ) O(n^{1.5}) O(n1.5)),更加厉害的做法是直接用线段树合并存每个节点的right集合,据 y w w yww yww大佬说,这个复杂度 O ( n l o g 2 n ) O(n log^2n) O(nlog2n) 是对的
不过这道题没有那么多问题,因为统计的信息比较简单,所以每次往上跳每个节点最多只会被经过一次,复杂度是 O ( n ) O(n) O(n) 的
一边建自动机一边往上统计size就可以了,最后每个串在SAM上跑一次统计答案
#include
#include
#include
#define N 200010
using namespace std;
char S[N];
int l[N],s[N][26],mx[N],sz[N],f[N];
int n,m,cnt=1,lst=1,r[N],v[N],p[N],w[N];
inline int extend(int c){
int p=lst,q=s[p][c],np,nq;
if(q){
if(mx[q]==mx[p]+1) return lst=q;
else{
nq=++cnt;
mx[nq]=mx[p]+1;
f[nq]=f[q]; f[q]=nq;
memcpy(s[nq],s[q],26<<2);
sz[nq]=sz[q]; ::p[nq]=::p[q];
for(;p&&s[p][c]==q;p=f[p]) s[p][c]=nq;
}
return lst=nq;
}
lst=np=++cnt; mx[np]=mx[p]+1;
for(;p&&!s[p][c];p=f[p]) s[p][c]=np;
if(!p) return f[np]=1;
q=s[p][c];
if(mx[q]==mx[p]+1) f[np]=q;
else{
nq=++cnt;
mx[nq]=mx[p]+1;
f[nq]=f[q]; f[q]=f[np]=nq;
memcpy(s[nq],s[q],26<<2);
sz[nq]=sz[q]; ::p[nq]=::p[q];
for(;p&&s[p][c]==q;p=f[p]) s[p][c]=nq;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%s",S+l[i]); lst=1;
l[i+1]=l[i]+strlen(S+l[i]);
for(int j=l[i];j<l[i+1];++j){
extend(S[j]-'a');
for(int x=lst;x;x=f[x])
if(p[x]<i){ ++sz[x]; p[x]=i; } else break;
}
}
for(int i=1;i<=cnt;++i) ++v[mx[i]];
for(int i=1;i<=cnt;++i) v[i]+=v[i-1];
for(int i=cnt;i;--i) r[v[mx[i]]--]=i;
for(int i=1,p;i<=cnt;++i){
p=r[i]; w[p]=w[f[p]];
if(sz[p]>=m) w[p]+=mx[p]-mx[f[p]];
}
for(int i=1;i<=n;++i){
long long A=0;
for(int x=1,j=l[i];j<l[i+1];++j){
x=s[x][S[j]-'a']; A+=w[x];
}
printf("%lld ",A);
}
}
Bzoj3926
题目有一个非常关键的信息:叶子节点 < = 20 <=20 <=20个
于是可以把整颗树看成一个Trie来做,由于每条路径必然会在某一个叶子节点作为根的时候变成Trie上面的一段,于是可以对每个叶子节点为根建立一个Trie上的SAM
· 严格来说,这个树不是一个Trie,因为同一个节点的两个子节点可以是相同的字母的出边
· 但是神奇的是,广义SAM在这种情况也是试用的
这一点还是之后再尝试理解吧,相比起一般的广义SAM,还有一种写法是每次强制建立新的节点,这种写法和一般的广义SAM完全等价,不过会多用一些节点
#include
#include
#include
#define N 2000010
using namespace std;
struct edge{ int v,nt; } G[N];
int s[N][11],mx[N],f[N],d[N];
int n,m,cnt=1,t=0,lst=1,c[N],h[N];
inline void adj(int x,int y){
G[++t]=(edge){y,h[x]}; h[x]=t;
G[++t]=(edge){x,h[y]}; h[y]=t;
}
inline int extend(int p,int c){
int np=++cnt,q,nq;
mx[np]=mx[p]+1;
for(;p&&!s[p][c];p=f[p]) s[p][c]=np;
if(!p){ f[np]=1; return np; }
q=s[p][c];
if(mx[q]==mx[p]+1) f[np]=q;
else{
nq=++cnt;
mx[nq]=mx[p]+1;
f[nq]=f[q]; f[q]=f[np]=nq;
memcpy(s[nq],s[q],40);
for(;p&&s[p][c]==q;p=f[p]) s[p][c]=nq;
}
return np;
}
inline int Extend(int p,int c){
int q=s[p][c],nq;
if(q){
if(mx[q]==mx[p]+1) return q;
else{
nq=++cnt;
mx[nq]=mx[p]+1;
f[nq]=f[q]; f[q]=nq;
memcpy(s[nq],s[q],40);
for(;p&&s[p][c]==q;p=f[p]) s[p][c]=nq;
return nq;
}
} return extend(p,c);
}
inline void dfs(int x,int p,int lst){
int y=Extend(lst,c[x]);
for(int v,i=h[x];i;i=G[i].nt)
if((v=G[i].v)!=p) dfs(v,x,y);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",c+i);
for(int x,y,i=1;i<n;++i){
scanf("%d%d",&x,&y);
adj(x,y); ++d[x]; ++d[y];
}
for(int i=1;i<=n;++i)
if(d[i]==1) dfs(i,0,1);
long long A=0;
for(int i=1;i<=cnt;++i) A+=mx[i]-mx[f[i]];
printf("%lld\n",A);
}
Bzoj2780
这个明显就是AC自动机的活啊,为什么一定要广义SAM呢
nan道是因为模板串总长比较短?
#include
#include
#include
#define N 360010
using namespace std;
char S[N],c[N];
int s[N][26],f[N],lst[N],sz[N];
int n,m,l[N],cnt=1,q[N],p[N],A[N],e[N];
inline void insert(char* t,int r){
int x=1;
for(;*t;++t){
if(!s[x][*t-'a'])
s[x][*t-'a']=++cnt;
x=s[x][*t-'a'];
}
if(sz[x]) e[sz[x]]=r;
sz[x]=r;
}
inline void build(){
int l=1,r=0;
for(int i=0;i<26;++i)
if(!s[1][i]) s[1][i]=1;
else f[s[1][i]]=1,q[++r]=s[1][i];
for(int x;l<=r;++l){
x=q[l];
for(int i=0;i<26;++i)
if(!s[x][i]) s[x][i]=s[f[x]][i];
else f[s[x][i]]=s[f[x]][i],q[++r]=s[x][i];
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%s",S+l[i]);
l[i+1]=l[i]+strlen(S+l[i]);
}
for(int i=1;i<=m;++i){
scanf("%s",c); insert(c,i);
}
build();
for(int i=1;i<=n;++i){
int x=1;
for(int j=l[i];j<l[i+1];++j){
x=s[x][S[j]-'a'];
for(int y=x;y;y=f[y])
if(p[y]==i) break; else ++A[sz[y]],p[y]=i;
}
}
for(int i=m;i;--i) if(e[i]) A[i]=A[e[i]];
for(int i=1;i<=m;++i) printf("%d\n",A[i]);
}
11.25 11.25 11.25更新
Bzoj3670
是一个KMP好题也,仿照kmp的做法就可以做啦
你问我为什么代码写的是SAM?因为我当时不会这种做法啦
不过直到今天我才用这份代码卡过去的。一直90ptsTLE
大概就是在parent树上面做倍增,每次建树只考虑那些是前缀的节点,每次扩展自动机后倍增到一个尽可能长的父亲节点,复杂度 O ( n log n ) O(n\log n) O(nlogn)
当时真的不知道脑子在想什么写的一堆奇怪的东西,比如这里面的sz其实就是mx,算了懒得改
#pragma GCC opitmize("O3")
#pragma G++ opitmize("O3")
#include
#include
#include
#define N 2000010
#define M 1000000007
using namespace std;
char str[N]; long long ans=1;
int s[N][26],mx[N],sz[N],f[N];
int n,lst=1,cnt=1,a[21][N];
inline int extend(int c){
register int p=lst,np=lst=++cnt,q,nq;
mx[np]=mx[p]+1; sz[np]=1; memset(s[cnt],0,26<<2);
for(;p&&!s[p][c];p=f[p]) s[p][c]=np;
if(!p) { f[np]=1; goto end; }
q=s[p][c];
if(mx[q]==mx[p]+1) f[np]=q;
else{
nq=++cnt;
mx[nq]=mx[p]+1;
f[nq]=f[q]; f[q]=f[np]=nq;
memcpy(s[nq],s[q],26<<2);
for(;p&&s[p][c]==q;p=f[p]) s[p][c]=nq;
p=f[nq]; a[0][nq]=sz[p]?p:a[0][p];
for(int j=1;j<=15;++j) a[j][nq]=a[j-1][a[j-1][nq]];
}
end:sz[np]+=sz[p=f[np]];
a[0][np]=sz[p]?p:a[0][p];
for(int j=1;j<=15;++j)
a[j][np]=a[j-1][a[j-1][np]];
p=np;
for(int j=15;~j;--j)
while((mx[a[j][p]]<<1)>mx[np])p=a[j][p];
p=a[0][p];
p!=np?ans=ans*(sz[p]+1)%M:0;
}
int _18520(){
memset(sz,0,sizeof sz);
memset(s[1],0,26<<2);
scanf("%s",str+1); n=strlen(str+1);
for(int i=1;i<=n;++i) extend(str[i]-'a');
printf("%lld\n",ans);
}
int main(){
int T; scanf("%d",&T);
for(;T--;_18520())ans=cnt=lst=1;
}
Codeforces471D
有点太裸了吧,差分后KMP即可。
↓↓↓↓↓↓非常特别的kmp写法↓↓↓↓↓↓
#include
#include
#include
#define N 200010
using namespace std;
int n,m,s[N],c[N],nt[N];
int main(){
scanf("%d%d",&m,&n);
for(int i=0;i<m;++i) scanf("%d",s+i);
for(int i=0;i<n;++i) scanf("%d",c+i);
if(n==1) return 0&printf("%d\n",m);
if(m==1) return 0&puts("0");
n--; m--;
for(int i=m;i;--i) s[i]-=s[i-1];
for(int i=n;i;--i) c[i]-=c[i-1];
for(int i=0;i<m;++i) s[i]=s[i+1];
for(int i=0;i<n;++i) c[i]=c[i+1];
for(int i=1,j;i<n;++i)
for(j=i;j;) if(c[j=nt[j]]==c[i]){ nt[i+1]=j+1; break; }
int A=0;
for(int i=0,j=0;i<m;++i){
if(j<n && s[i]==c[j]) ++j;
else while(j) if(c[j=nt[j]]==s[i]){ ++j; break; }
if(j==n) ++A;
}
printf("%d\n",A);
}
Bzoj1009
非常经典的AC自动机上dp
f i , j f_{i,j} fi,j表示长度为i,匹配到了自动机上面第j个节点的方案数
因为j很小而i很大所以用矩阵加速
#include
#include
#include
using namespace std;
char S[30];
int n,m,M,s[30][10],f[30],q[30];
inline void ad(int& x,int y){ x=(x+y)%M; }
struct Mat{
int s[30][30],n,m;
inline void set(int x,int y){
n=x; m=y; memset(s,0,sizeof s);
}
inline Mat operator* (Mat b){
Mat c; c.set(n,b.m);
for(int i=1;i<=n;++i)
for(int k=1;k<=m;++k)
for(int j=1;j<=b.m;++j)
ad(c.s[i][j],s[i][k]*b.s[k][j]);
return c;
}
} a,b;
int main(){
scanf("%d%d%d%s",&n,&m,&M,S);
int x=1;
for(int i=0;i<m;++i) x=s[x][S[i]-48]=i+2;
int l=1,r=0;
for(int i=0;i<=9;++i)
if(!s[1][i]) s[1][i]=1;
else f[q[++r]=s[1][i]]=1;
for(int x;l<=r;++l){
x=q[l];
for(int i=0;i<=9;++i)
if(!s[x][i]) s[x][i]=s[f[x]][i];
else f[q[++r]=s[x][i]]=s[f[x]][i];
}
b.set(m,m);
for(int j=1;j<=m;++j)
for(int k=0;k<=9;++k){
ad(b.s[j][s[j][k]],1);
}
a.set(1,m);
a.s[1][1]=1;
for(;n;b=b*b,n>>=1) if(n&1) a=a*b;
int A=0;
for(int j=1;j<=m;++j) ad(A,a.s[1][j]);
printf("%d\n",A);
}
Bzoj2085
比较有趣的一个dp?
其实还是有点难想
我们考虑求出一个矩阵 S S S这里 S i , j S_{i,j} Si,j的定义是这样的:
最长的len使得串i长度为len的后缀=串j长度为len的前缀
反正有点像扩展kmp求的那个东西
不过我们用哈希求就可以了
接下来就可以dp了
F i , j F_{i,j} Fi,j表示出现了i个名字,最后一个出现的名字为j的最短长度
由于互不包含,枚举下一个出现的名字k,得到转移
F i + 1 , k = m i n { F i , j + S i , j } F_{i+1,k}=min\{F_{i,j}+S_{i,j}\} Fi+1,k=min{Fi,j+Si,j}拿个矩阵来加速转移就可以了
#include
#include
#include
#define LL long long
using namespace std;
char S[2000100];
int n,m,l[210]; LL h[2000010],bas[2000010];
inline LL gmin(LL& x,LL y){ x>y?x=y:0; }
struct Mat{
LL s[220][220]; int n,m;
inline void set(int x,int y){
n=x; m=y; memset(s,0x3f,sizeof s);
}
inline Mat operator* (Mat b){
Mat c; c.set(n,b.m);
for(int i=1;i<=n;++i)
for(int k=1;k<=m;++k)
for(int j=1;j<=b.m;++j)
gmin(c.s[i][j],s[i][k]+b.s[k][j]);
return c;
}
} a,b;
inline LL gH(int l,int r){
return h[r]-h[l-1]*bas[r-l+1];
}
int main(){
l[1]=1; scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%s",S+l[i]);
l[i+1]=l[i]+strlen(S+l[i]);
}
for(int i=*bas=1;i<l[n+1];++i){
bas[i]=bas[i-1]*131;
h[i]=h[i-1]*131+S[i];
}
a.set(1,n); b.set(n,n);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
for(int k=min(l[j+1]-l[j],l[i+1]-l[i])-2;k>=-1;--k)
if(gH(l[j],l[j]+k)==gH(l[i+1]-1-k,l[i+1]-1)){
b.s[i][j]=l[j+1]-l[j]-k-1;
break;
}
}
for(int i=1;i<=n;++i) a.s[1][i]=l[i+1]-l[i];
for(--m;m;b=b*b,m>>=1) if(m&1) a=a*b;
LL A=1ll<<62;
for(int i=1;i<=n;++i) gmin(A,a.s[1][i]);
printf("%lld\n",A);
}
Codeforces494B
这也是一道dp,kmp只是辅助
先求出所有匹配的位置,记 b i b_i bi表示i是否是一个匹配的结尾
考虑dp, F i F_i Fi表示长度为i的方案数, S i S_i Si表示 F 1 − F i F1-Fi F1−Fi的和
转移的方式有3种
1.舍弃当前位置
2.当前位置到上一个匹配点作为一段,舍弃前面其余的
3.当前位置到上一个匹配点作为一段,不舍弃前面其余的
那么一起就是
F i = F i − 1 + S l a s t − 1 + l a s t F_i=F_{i-1}+S_{last-1}+last Fi=Fi−1+Slast−1+lastlast是上一个匹配点的开头
#include
#include
#include
#define N 100010
#define M 1000000007
using namespace std;
int f[N],s[N],n,m,nt[N],b[N]; char t[N],c[N];
inline void ad(int& x,int y){ x=(x+y)%M; }
int main(){
scanf("%s%s",t,c);
m=strlen(t); n=strlen(c);
for(int i=1,j;i<n;++i)
for(j=i;j;) if(c[j=nt[j]]==c[i]){ nt[i+1]=j+1; break; }
for(int i=0,j=0;i<m;++i){
if(j<n && t[i]==c[j]) ++j;
else while(j) if(c[j=nt[j]]==t[i]){ ++j; break; }
if(j==n) b[i+1]=1;
}
int lst=0;
for(int i=1;i<=m;++i){
if(b[i]) lst=i-n+1;
f[i]=f[i-1];
if(lst) ad(f[i],s[lst-1]+lst);
s[i]=s[i-1]; ad(s[i],f[i]);
}
printf("%d\n",f[m]);
}
Bzoj1355
稍加分析,周期+border=n,求next数组即可
code(in Latex)
# i n c l u d e < s t d i o . h > \#include<stdio.h> #include<stdio.h>
u s i n g n a m e s p a c e s t d ; using\ namespace\ std; using namespace std;
c h a r s [ 1000010 ] ; i n t n , n t [ 1000010 ] ; char\ s[1000010];\ int\ n,nt[1000010]; char s[1000010]; int n,nt[1000010];
i n t m a i n ( ) { int\ main()\{ int main(){
s c a n f ( " % d % s " , & n , s ) ; \quad scanf("\%d\%s",\&n,s); scanf("%d%s",&n,s);
f o r ( i n t i = 1 , j ; i < n ; + + i ) \quad for(int\ i=1,j;i<n;++i) for(int i=1,j;i<n;++i)
f o r ( j = i ; j ; ) i f ( s [ j = n t [ j ] ] = = s [ i ] ) { n t [ i + 1 ] = j + 1 ; b r e a k ; } \quad \quad for(j=i;j;)\ if(s[j=nt[j]]==s[i])\{\ nt[i+1]=j+1;\ break;\ \} for(j=i;j;) if(s[j=nt[j]]==s[i]){ nt[i+1]=j+1; break; }
p r i n t f ( " % d \ n " , n − n t [ n ] ) ; \quad printf("\%d\backslash n",n-nt[n]); printf("%d\n",n−nt[n]);
} \} }