字符串专题1

.
都是些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 F1Fi的和
转移的方式有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=Fi1+Slast1+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",nnt[n]);
} \} }

你可能感兴趣的:(OI,数据结构,----动态树,字符串,----后缀自动机,----后缀数组,--------广义后缀自动机,----AC自动机,----平衡树,----Hash)