字符串题目集

题目:
SCOI2012 喵星球上的点名
划分成回文串
ZJOI2015 诸神眷顾的幻想乡
Alphabetic Tree
Little Goth
Asterisk Substrings
NOI2018 你的名字

SCOI2012 喵星球上的点名

先将每只猫的姓和名和询问串中间加分隔符串在一起并求出后缀数组。对于一个询问串,可以在后缀数组上定位出含有该串为子串的后缀区间。把后缀起始位置的猫的编号看做颜色,则要求区间颜色数量,离线并用树状数组即可。
另外还要求覆盖每种颜色的区间数量。还是按区间左端点排序,从左往右遍历,遇到区间左端点就在左端点出加 1 1 1,该位置的颜色的答案加上当前位置和上个该颜色位置的左端点个数,遇到右端点就在左端点出减 1 1 1。用树状数组维护即可。
时间复杂度 O ( ∣ S ∣ log ⁡ 2 ∣ S ∣ ) O(|S|\log_2|S|) O(Slog2S),空间复杂度 O ( ∣ S ∣ ) O(|S|) O(S)

#include<stdio.h>
#include<algorithm>
#define R register int
#define I inline
#define N 100000
#define M 400001
I int Min(const int x,const int y){
	return x<y?x:y;
}
int s[M],col[M],sa[M],rk[M],hei[M],Last[50001],C[M],f[19][M],pos[N],len[N],ans[N];
I int Read(int*S,int*C,const int x){
	int len;
	scanf("%d",&len);
	S[0]=-1;
	for(R i=1;i<=len;i++){
		C[i]=x;
		scanf("%d",S+i);
	}
	return len;
}
I void BuildSA(const int n){
	for(R i=1;i<=n;i++){
		sa[i]=i;
	}
	auto Compare=[](int x,int y){
		return s[x]<s[y];
	};
	std::sort(sa+1,sa+n+1,Compare);
	static int ct[M],temrk[M],sa2[M];
	int*rk1=rk,*rk2=temrk,sum=0,cur;
	for(R j=1;j<=n;j++){
		if(s[sa[j]]!=s[sa[j-1]]){
			sum++;
		}
		rk1[sa[j]]=sum;
	}
	for(R i=1;i<n&&sum!=n;i<<=1){
		cur=i;
		for(R j=1;j<=i;j++){
			sa2[j]=n-i+j;
		}
		for(R j=1;j<=n;j++){
			if(sa[j]>i){
				cur++;
				sa2[cur]=sa[j]-i;
			}
		}
		for(R i=0;i<=sum;i++){
			ct[i]=0;
		}
		for(R j=1;j<=n;j++){
			ct[rk1[j]]++;
		}
		for(R j=1;j<=sum;j++){
			ct[j]+=ct[j-1];
		}
		for(R j=n;j!=0;j--){
			int&c=ct[rk1[sa2[j]]];
			sa[c]=sa2[j];
			c--;
		}
		sum=0;
		for(R j=1;j<=n;j++){
			if(rk1[sa[j]]!=rk1[sa[j-1]]||(sa[j]+i>n?0:rk1[sa[j]+i])!=(sa[j-1]+i>n?0:rk1[sa[j-1]+i])){
				sum++;
			}
			rk2[sa[j]]=sum;
		}
		int*t=rk1;
		rk1=rk2;
		rk2=t;
	}
	if(rk1!=rk){
		for(R i=1;i<=n;i++){
			rk[i]=rk1[i];
		}
	}
	cur=0;
	for(R i=1;i<=n;i++){
		if(cur!=0){
			cur--;
		}
		if(rk[i]!=1){
			int pos=sa[rk[i]-1];
			while(pos+cur<=n&&i+cur<=n&&s[pos+cur]==s[i+cur]){
				cur++;
			}
			hei[rk[i]]=cur;
		}
	}
}
struct Segment{
	int Id,Lf,Rt;
	I void Init(int l,int r,int d){
		Id=d;
		Lf=l;
		Rt=r;
	}
	I friend bool operator<(Segment A,Segment B){
		return A.Rt<B.Rt;
	}
}seg[N];
I void Add(int x,const int d,const int n){
	for(R i=x;i<=n;i+=i&-i){
		C[i]+=d;
	}
}
I int GetSum(int x){
	int res=0;
	for(R i=x;i!=0;i&=i-1){
		res+=C[i];
	}
	return res;
}
int main(){
	int n,q,tot=0,cur=0,x;
	scanf("%d%d",&n,&q);
	for(R i=1;i<=n;i++){
		tot+=Read(s+tot,col+tot,i)+1;
		tot+=Read(s+tot,col+tot,i)+1;
	}
	for(R i=0;i!=q;i++){
		pos[i]=tot+1;
		len[i]=Read(s+tot,col+tot,0);
		tot+=len[i]+1;
	}
	BuildSA(tot);
	for(R i=1;i!=tot;i++){
		f[0][i]=hei[i+1];
	}
	for(R i=1;1<<i<tot;i++){
		for(R j=1;j<=tot-(1<<i);j++){
			f[i][j]=Min(f[i-1][j],f[i-1][j+(1<<i-1)]);
		}
	}
	for(R i=0;i!=q;i++){
		pos[i]=rk[pos[i]];
		int l=pos[i],r;
		r=pos[i];
		for(R j=18;j!=-1;j--){
			if(l>=1<<j&&f[j][l-(1<<j)]>=len[i]){
				l-=1<<j;
			}
		}
		for(R j=18;j!=-1;j--){
			if(r+(1<<j)<=tot&&f[j][r]>=len[i]){
				r+=1<<j;
			}
		}
		seg[i].Init(l,r,i);
	}
	std::sort(seg,seg+q);
	for(R i=1;i<=tot;i++){
		x=col[sa[i]];
		if(x!=0){
			if(Last[x]!=0){
				Add(Last[x],-1,tot);
			}
			Add(i,1,tot);
			Last[x]=i;
		}
		while(cur!=q&&seg[cur].Rt<=i){
			ans[seg[cur].Id]=GetSum(seg[cur].Rt)-GetSum(seg[cur].Lf-1);
			cur++;
		}
	}
	for(R i=0;i!=q;i++){
		printf("%d\n",ans[i]);
	}
	for(R i=1;i<=n;i++){
		Add(Last[i],-1,tot);
		Last[i]=ans[i]=0;
	}
	for(R i=0;i!=q;i++){
		Add(seg[i].Lf,1,tot);
	}
	cur=0;
	for(R i=1;i<=tot;i++){
		x=col[sa[i]];
		if(x!=0){
			ans[x]+=GetSum(i)-GetSum(Last[x]);
			Last[x]=i;
		}
		while(cur!=q&&seg[cur].Rt<=i){
			Add(seg[cur].Lf,-1,tot);
			cur++;
		}
	}
	for(R i=1;i<=n;i++){
		printf("%d ",ans[i]);
	}
	return 0;
}
划分成回文串

先建出回文自动机。由于以每个为后缀的回文串长度形成最多 O ( log ⁡ 2 n ) O(\log_2n) O(log2n) 个等差数列,因此在回文自动机上优化DP即可。
时间复杂度 O ( n log ⁡ 2 n ) O(n \log_2n) O(nlog2n),空间复杂度 O ( n ) O(n) O(n)

#include<stdio.h>
#include<string.h>
#define R register int
#define N 300002
char s[N];
int Fail[N],Top[N],Len[N],Del[N],Child[N][26],f[N],g[N];
int main(){
	Len[1]=-1;
	Fail[0]=1;
	scanf("%s",s+1);
	int n=strlen(s+1),ct=1,Last=0,x,p,q,y;
	for(R i=1;i<=n;i++){
		f[i]=N;
		x=s[i]-'a';
		p=Last;
 		while(s[i]!=s[i-1-Len[p]]){
			p=Fail[p];
		}
		if(Child[p][x]==0){
			ct++;
			q=ct;
			Len[q]=Len[p]+2;
			y=Fail[p];
			while(s[i]!=s[i-1-Len[y]]){
				y=Fail[y];
			}
			Fail[q]=Child[y][x];
			Del[q]=Len[q]-Len[Fail[q]];
			Child[p][x]=q;
			if(Del[q]==Del[Fail[q]]){
				Top[q]=Top[Fail[q]];
			}else{
				Top[q]=Fail[q];
			}
		}
		Last=Child[p][x];
		for(R j=Last;j>1;j=Top[j]){
			g[j]=f[i-Len[Top[j]]-Len[j]+Len[Fail[j]]];
			if(Del[j]==Del[Fail[j]]&&g[j]>g[Fail[j]]){
				g[j]=g[Fail[j]];
			}
			if(g[j]+1<f[i]){
				f[i]=g[j]+1;
			}
		}
	}
	printf("%d",f[n]);
	return 0;
}
ZJOI2015诸神眷顾的幻想乡

如果只有一个串,求本质不同子串个数,则建出后缀自动机即可。
由于树只有最多 20 20 20个叶子,像主席树一般以每个叶子当根建出广义后缀自动机即可。
时空复杂度 O ( n c ) O(nc) O(nc)

#include<stdio.h>
#include<vector>
using namespace std;
#define R register int
struct SAMNode{
	int Par,Len,S[10];
}t[4000001];
int ct=1,s[100001];
inline int AddNode(int x,int Last,int&c){
	ct++;
	int now=ct,p=Last;
	t[now].Len=t[p].Len+1;
	while(p!=0&&t[p].S[x]==0){
		t[p].S[x]=now;
		p=t[p].Par;
	}
	if(p==0){
		t[now].Par=1;
	}else{
		int q=t[p].S[x];
		if(t[p].Len+1==t[q].Len){
			t[now].Par=q;
		}else{
			ct++;
			int nq=ct;
			t[nq].Len=t[p].Len+1;
			for(R i=0;i!=c;i++){
				t[nq].S[i]=t[q].S[i];
			}
			t[nq].Par=t[q].Par;
			t[q].Par=t[now].Par=nq;
			while(t[p].S[x]==q&&p!=0){
				t[p].S[x]=nq;
				p=t[p].Par;
			}
		}
	}
	return now;
}
vector<int>G[100001];
inline void DFS(int x,int F,int Last,int&c){
	int cur=AddNode(s[x],Last,c);
	for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
		if(*T!=F){
			DFS(*T,x,cur,c);
		}
	}
}
int main(){
	int n,c,x,y;
	scanf("%d%d",&n,&c);
	for(R i=1;i<=n;i++){
		scanf("%d",s+i);
	}
	for(R i=1;i!=n;i++){
		scanf("%d%d",&x,&y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	for(R i=1;i<=n;i++){
		if(G[i].size()==1){
			DFS(i,0,1,c);
		}
	}
	long long ans=0;
	for(R i=2;i<=ct;i++){
		ans+=t[i].Len-t[t[i].Par].Len;
	}
	printf("%lld",ans);
	return 0;
}
Alphabetic Tree

先将所有给出的串和树上每条重链的上行串和下行串拼起来,求出后缀数组。每次询问只需要在后缀数组中找到以询问的串为前缀的区间然后主席树查询即可。
由于询问的串被拆分为了多条链,需要分布二分。每次新加入一个串,先找出新串在后缀数组中的位置,然后二分左右端点,二分时比较字典序即可。

#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
#define R register int
#define I inline
#define N 100001
#define M 600001
#define K 2000000
struct Edge{
	int End;
	char Tag;
};
I Edge Pair(int y,char c){
	Edge Res;
	Res.End=y;
	Res.Tag=c;
	return Res;
}
vector<Edge>G[N];
int h[N],f[N],sz[N],pos[N],Top[N],dep[N],PosU[N],PosD[N],rk[M],sa[M],hei[M],col[M],g[20][M];
char s[M],fe[N];
I void PreDFS(int x,int F){
	f[x]=F;
	dep[x]=dep[F]+1;
	sz[x]=1;
	for(Edge T:G[x]){
		int v=T.End;
		if(v!=F){
			fe[v]=T.Tag;
			PreDFS(v,x);
			sz[x]+=sz[v];
			if(sz[v]>sz[h[x]]){
				h[x]=v;
			}
		}
	}
}
I void ReDFS(int x,int t){
	Top[x]=t;
	if(h[x]!=0){
		ReDFS(h[x],t);
		for(Edge T:G[x]){
			int v=T.End;
			if(v!=f[x]&&v!=h[x]){
				ReDFS(v,v);
			}
		}
	}
}
I void BuildSA(const int n){
	static int ct[M],temrk[M],sa2[M];
	for(R i=1;i<=n;i++){
		ct[s[i]-'a']++;
	}
	for(R i=1;i!=27;i++){
		ct[i]+=ct[i-1];
	}
	for(R i=1;i<=n;i++){
		sa[ct[s[i]-'a']]=i;
		ct[s[i]-'a']--;
	}
	int sum=0,cur,*rk1=rk,*rk2=temrk;
	for(R i=1;i<=n;i++){
		if(s[sa[i]]!=s[sa[i-1]]){
			sum++;
		}
		rk[sa[i]]=sum;
	}
	for(R i=1;sum!=n;i<<=1){
		for(R j=0;j!=i;j++){
			sa2[j+1]=n-j;
		}
		cur=i;
		for(R j=1;j<=n;j++){
			if(sa[j]>i){
				cur++;
				sa2[cur]=sa[j]-i;
			}
		}
		for(R j=0;j<=sum;j++){
			ct[j]=0;
		}
		for(R j=1;j<=n;j++){
			ct[rk1[j]]++;
		}
		for(R j=1;j<=sum;j++){
			ct[j]+=ct[j-1];
		}
		for(R j=n;j!=0;j--){
			int&c=ct[rk1[sa2[j]]];
			sa[c]=sa2[j];
			c--;
		}
		sum=0;
		for(R j=1;j<=n;j++){
			if(rk1[sa[j]]!=rk1[sa[j-1]]||(sa[j]+i>n?0:rk1[sa[j]+i])!=(sa[j-1]+i>n?0:rk1[sa[j-1]+i])){
				sum++;
			}
			rk2[sa[j]]=sum;
		}
		int*t=rk1;
		rk1=rk2;
		rk2=t;
	}
	if(rk1!=rk){
		for(R i=1;i<=n;i++){
			rk[i]=rk1[i];
		}
	}
	cur=0;
	for(R i=1;i<=n;i++){
		if(cur!=0){
			cur--;
		}
		if(rk[i]!=1){
			int pos=sa[rk[i]-1];
			while(i+cur<=n&&pos+cur<=n&&s[i+cur]==s[pos+cur]){
				cur++;
			}
			hei[rk[i]]=cur;
		}
	}
}
namespace SEG{
	int Root[M],Ls[K],Rs[K],Sum[K],Tot,Len;
	I void Init(const int m){
		Len=m;
	}
	I void GetNode(int&x){
		Tot++;
		x=Tot;
	}
	I void Insert(int p1,int p2,int lf,int rt,const int x){
		Sum[p2]=Sum[p1]+1;
		if(lf!=rt){
			int mid=lf+rt>>1;
			if(x>mid){
				GetNode(Rs[p2]);
				Ls[p2]=Ls[p1];
				Insert(Rs[p1],Rs[p2],mid+1,rt,x);
			}else{
				GetNode(Ls[p2]);
				Rs[p2]=Rs[p1];
				Insert(Ls[p1],Ls[p2],lf,mid,x);
			}
		}
	}
	I void Append(int x,int c){
		if(c==0){
			Root[x]=Root[x-1];
		}else{
			GetNode(Root[x]);
			Insert(Root[x-1],Root[x],1,Len,c);
		}
	}
	I int GetAns(int p1,int p2,int lf,int rt,const int l,const int r){
		if(l<=lf&&rt<=r){
			return Sum[p2]-Sum[p1];
		}
		int mid=lf+rt>>1,res=0;
		if(l<=mid){
			res=GetAns(Ls[p1],Ls[p2],lf,mid,l,r);
		}
		if(r>mid){
			res+=GetAns(Rs[p1],Rs[p2],mid+1,rt,l,r);
		}
		return res;
	}
	I int GetSum(int l,int r,int ql,int qr){
		return GetAns(Root[l-1],Root[r],1,Len,ql,qr);
	}
}
int main(){
	int n,m,q,x,y,totlen=-1,len,ql,qr,l,r,cl,cr;
	scanf("%d%d%d",&n,&m,&q);
	for(R i=1;i!=n;i++){
		scanf("%d%d",&x,&y);
		char c;
		scanf(" %c",&c);
		G[x].push_back(Pair(y,c));
		G[y].push_back(Pair(x,c));
	}
	PreDFS(1,0);
	ReDFS(1,1);
	for(R i=1;i<=m;i++){
		totlen++;
		s[totlen]='{';
		pos[i]=totlen+1;
		scanf("%s",s+pos[i]);
		len=strlen(s+pos[i]);
		for(R j=1;j<=len;j++){
			col[totlen+j]=i;
		}
		totlen+=len;
	}
	for(R i=2;i<=n;i++){
		if(G[i].size()==1){
			totlen++;
			s[totlen]='{';
			y=f[Top[i]];
			for(R j=i;j!=y;j=f[j]){
				if(j!=1){
					totlen++;
					PosU[j]=totlen;
					s[totlen]=fe[j];
				}
			}
			totlen++;
			s[totlen]='{';
			for(R j=Top[i];j!=0;j=h[j]){
				if(j!=1){
					totlen++;
					PosD[j]=totlen;
					s[totlen]=fe[j];
				}
			}
		}
	}
	BuildSA(totlen);
	for(R i=1;i!=totlen;i++){
		g[0][i]=hei[i+1];
	}
	for(R i=1;1<<i<totlen;i++){
		for(R j=1;j<=totlen-(1<<i);j++){
			x=g[i-1][j];
			y=g[i-1][j+(1<<i-1)];
			g[i][j]=x<y?x:y;
		}
	}
	SEG::Init(m);
	for(R i=1;i<=totlen;i++){
		SEG::Append(i,col[sa[i]]);
	}
	for(R i=0;i!=q;i++){
		scanf("%d%d%d%d",&x,&y,&ql,&qr);
		vector<pair<int,int>>Up,Down,Sub;
		while(Top[x]!=Top[y]){
			if(dep[Top[x]]>dep[Top[y]]){
				Up.push_back(make_pair(x,Top[x]));
				x=f[Top[x]];
			}else{
				Down.push_back(make_pair(y,Top[y]));
				y=f[Top[y]];
			}
		}
		if(dep[x]>dep[y]){
			Up.push_back(make_pair(x,h[y]));
		}else if(dep[x]<dep[y]){
			Down.push_back(make_pair(y,h[x]));
		}
		for(auto T:Up){
			Sub.push_back(make_pair(rk[PosU[T.first]],dep[T.first]-dep[T.second]+1));
		}
		for(auto T=Down.rbegin();T!=Down.rend();T++){
			Sub.push_back(make_pair(rk[PosD[T->second]],dep[T->first]-dep[T->second]+1));
		}
		l=1;
		r=totlen;
		len=0;
		for(auto T=Sub.begin();T!=Sub.end()&&l<=r;T++){
			cl=cr=T->first;
			for(R j=19;j!=-1;j--){
				if(cl>=1<<j&&g[j][cl-(1<<j)]>=T->second){
					cl-=1<<j;
				}
				if(cr+(1<<j)<=totlen&&g[j][cr]>=T->second){
					cr+=1<<j;
				}
			}
			int l2=l,r2=r,mid,res=M;
			while(l2<=r2){
				mid=l2+r2>>1;
				int tem=rk[sa[mid]+len];
				if(tem<cl){
					l2=mid+1;
				}else{
					res=mid;
					r2=mid-1;
				}
			}
			l=l2=res;
			r2=r;
			res=0;
			while(l2<=r2){
				mid=l2+r2>>1;
				int tem=rk[sa[mid]+len];
				if(tem>cr){
					r2=mid-1;
				}else{
					res=mid;
					l2=mid+1;
				}
			}
			r=res;
			len+=T->second;
		}
		if(l>r){
			puts("0");
		}else{
			printf("%d\n",SEG::GetSum(l,r,ql,qr));
		}
	}
	return 0;
}
Little Goth

首先经过贪心,发现最开始的位置 j j j 最大越好,这里需要求两个后缀的最长公共前缀,在后缀树上求最近公共祖先即可。
又经过贪心, j − i j-i ji 的值不会变大。到终点的方式只有三种,一种不传送,一种 i = j i=j i=j 传送多次,一种只以原来位置的串的子串传送一次。第一种情况直接统计答案。第二种情况预处理 d i , j d_{i,j} di,j 表示从第 i i i 中字符走到第 j j j 个位置的最小代价,枚举中转字符统计答案。
对于第三种情况有两种做法。首先根据贪心有传送到的位置 [ l , r ] [l,r] [l,r] 的左端点一定不在 k k k 的左边,且答案与 2 l − r 2l-r 2lr 相关。第一种做法枚举 l l l,二分找到 r r r 的最大值,这里要求 S l , r S_{l,r} Sl,r 是原位置串的子串,用后缀树上线段树合并即可。第二种在后缀树上DP。设 g i g_i gi 为以 i i i 为根的子树中位置最左且在 k k k 及之后的后缀开始的位置, f i f_i fi 为考虑以 i i i 节点为等价类的串的 [ 2 l − r ] [2l-r] [2lr] 最小值。则 f i f_i fi g i g_i gi,后缀树父亲DP值,后缀树失配点的DP值的最小值。将两种方法根号分治。
时间复杂度 O ( ( n + q ) n log ⁡ 2 n ) O((n+q)\sqrt n \log_2n) O((n+q)n log2n),空间复杂度 O ( n log ⁡ 2 n + q ) O(n \log_2 n+q) O(nlog2n+q)

#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define R register int
#define I inline
#define N 60001
I void Min(int&x,const int y){
	if(x>y){
		x=y;
	}
}
I void Swap(int&x,int&y){
	int t=x;
	x=y;
	y=t;
}
char s[30002];
int pos[30001],dis[26][30001],ans[30000],base[30000];
class SegmentForest{
	int Ls[1600000],Rs[1600000],Tot,Len;
	bool Tag[1600000];
	I void Insert(int p,int lf,int rt,const int x){
		Tag[p]=true;
		if(lf!=rt){
			int mid=lf+rt>>1;
			if(x>mid){
				GetNode(Rs[p]);
				Insert(Rs[p],mid+1,rt,x);
			}else{
				GetNode(Ls[p]);
				Insert(Ls[p],lf,mid,x);
			}
		}
	}
	I bool Check(int p,int lf,int rt,const int l,const int r){
		if(l<=lf&&rt<=r){
			return Tag[p];
		}
		int mid=lf+rt>>1;
		return l<=mid&&Ls[p]!=0&&Check(Ls[p],lf,mid,l,r)||r>mid&&Rs[p]!=0&&Check(Rs[p],mid+1,rt,l,r);
	}
	public:
		I void GetNode(int&x){
			Tot++;
			x=Tot;
		}
		I void InitForest(const int n){
			Len=n;
		}
		I void InsertElement(int root,int x){
			Insert(root,1,Len,x);
		}
		I int Merge(int x,int y){
			if(x==0||y==0){
				return x|y;
			}
			int p;
			GetNode(p);
			Tag[p]=Tag[x]||Tag[y];
			Ls[p]=Merge(Ls[x],Ls[y]);
			Rs[p]=Merge(Rs[x],Rs[y]);
			return p;
		}
		I bool CheckExist(int rt,int l,int r){
			return Check(rt,1,Len,l,r);
		}
}SEG;
class SAMTree{
	int Len[N],Root[N],Link[N],dep[N],lfpos[N],dp[N],ord[N],Par[N][16],Child[N][26],val[N],Last=1,Tot=1;
	vector<int>G[N];
	I void AddNode(int&x){
		Tot++;
		x=Tot;
	}
	I void DFS(int x){
		dep[x]=dep[Par[x][0]]+1;
		for(R i=1;Par[x][i-1]!=0;i++){
			Par[x][i]=Par[Par[x][i-1]][i-1];
		}
		if(val[x]!=0){
			SEG.GetNode(Root[x]);
			SEG.InsertElement(Root[x],val[x]);
		}
		for(int T:G[x]){
			DFS(T);
			Root[x]=SEG.Merge(Root[x],Root[T]);
		}
	}
	public:
		I int Append(int x,int c){
			int p=Last,cur;
			AddNode(cur);
			val[cur]=x;
			Last=cur;
			Len[cur]=Len[p]+1;
			while(p!=0&&Child[p][c]==0){
				Child[p][c]=cur;
				p=Par[p][0];
			}
			if(p==0){
				Par[cur][0]=1;
			}else{
				int q=Child[p][c];
				if(Len[p]+1==Len[q]){
					Par[cur][0]=q;
				}else{
					int nq;
					AddNode(nq);
					Len[nq]=Len[p]+1;
					for(R i=0;i!=26;i++){
						Child[nq][i]=Child[q][i];
					}
					Par[nq][0]=Par[q][0];
					Par[q][0]=Par[cur][0]=nq;
					while(Child[p][c]==q&&p!=0){
						Child[p][c]=nq;
						p=Par[p][0];
					}
				}
			}
			return Last;
		}
		I void InitTree(){
			for(int i=2;i<=Tot;i++){
				G[Par[i][0]].push_back(i);
				Link[i]=1;
			}
			for(R i=1;i<=Tot;i++){
				for(R j=0;j!=26;j++){
					int x=Child[i][j];
					if(Len[i]>Len[Link[x]]){
						Link[x]=i;
					}
				}
			}
			DFS(1);
			for(R i=1;i<=Tot;i++){
				ord[i]=i;
			}
			auto Compare=[&](int x,int y){return Len[x]<Len[y];};
			sort(ord+1,ord+Tot+1,Compare);
		}
		I int GetLCP(int x,int y){
			x=pos[x];
			y=pos[y];
			if(dep[x]<dep[y]){
				Swap(x,y);
			}
			int l=dep[x]-dep[y];
			for(R i=0;i!=16;i++){
				if((l>>i&1)==1){
					x=Par[x][i];
				}
			}
			if(x==y){
				return Len[x];
			}
			for(R i=15;i!=-1;i--){
				if(Par[x][i]!=Par[y][i]){
					x=Par[x][i];
					y=Par[y][i];
				}
			}
			return Len[Par[y][0]];
		}
		I int FindNode(int x,int l){
			for(R i=15;i!=-1;i--){
				if(Len[Par[x][i]]>=l){
					x=Par[x][i];
				}
			}
			return x;
		}
		I bool CheckSub(int l1,int r1,int l2,int r2){
			if(r2-l2<r1-l1){
				return false;
			}
			return SEG.CheckExist(Root[FindNode(pos[l1],r1-l1+1)],l2,r2-r1+l1);
		}
		I void DP(const int lim){
			for(R i=Tot;i!=0;i--){
				int x=ord[i];
				lfpos[x]=val[x]<lim?N:val[x];
				for(int T:G[x]){
					Min(lfpos[x],lfpos[T]);
				}
				dp[x]=lfpos[x]-Len[x];
			}
			dp[1]=N;
			for(R i=2;i<=Tot;i++){
				int x=ord[i];
				Min(dp[x],dp[Par[x][0]]);
				Min(dp[x],dp[Link[x]]);
			}
		}
		I int GetDP(int l,int r){
			int x=FindNode(pos[l],r-l+1);
			return dp[x];
		}
}SAM;
vector<int>Pos[26];
struct Node{
	int Id,Dis;
	I friend bool operator<(Node A,Node B){
		return A.Dis>B.Dis;
	}
};
I void BFS(const int n){
	static int d[30027];
	static bool vis[30027];
	for(R i=0;i!=26;i++){
		for(R j=1;j!=n+27;j++){
			d[j]=N;
			vis[j]=false;
		}
		priority_queue<Node>Q;
		Node Tem;
		Tem.Id=n+i+1;
		Tem.Dis=d[n+i+1]=0;
		Q.push(Tem);
		while(Q.empty()==false){
			int x=Q.top().Id;
			Q.pop();
			if(vis[x]==false){
				vis[x]=true;
				if(x>n){
					Tem.Dis=d[x];
					for(int T:Pos[x-n-1]){
						if(Tem.Dis<d[T]){
							d[T]=Tem.Dis;
							Tem.Id=T;
							Q.push(Tem);
						}
					}
				}else{
					Tem.Dis=d[x]+1;
					if(x!=1&&d[x-1]>Tem.Dis){
						d[x-1]=d[x]+1;
						Tem.Id=x-1;
						Q.push(Tem);
					}
					if(x!=n&&d[x+1]>Tem.Dis){
						d[x+1]=d[x]+1;
						Tem.Id=x+1;
						Q.push(Tem);
					}
					int y=s[x]-'a'+n+1;
					if(d[y]>Tem.Dis){
						d[y]=d[x]+1;
						Tem.Id=y;
						Q.push(Tem);
					}
				}
			}
		}
		for(R j=1;j<=n;j++){
			dis[i][j]=d[j];
		}
	}
}
struct Query{
	int Left,Right,Id,Target;
};
vector<Query>Q[199];
int main(){
	int n,q,x,l,r,z;
	scanf("%d%d",&n,&q);
	scanf("%s",s+1);
	for(int i=1;i<=n;i++){
		Pos[s[i]-'a'].push_back(i);
	}
	for(R i=n;i!=0;i--){
		pos[i]=SAM.Append(i,s[i]-'a');
	}
	BFS(n);
	SEG.InitForest(n);
	SAM.InitTree();
	const int BLOCK=151;
	for(R i=0;i!=q;i++){
		scanf("%d%d%d",&l,&x,&z);
		ans[i]=l<z?z-l:l-z;
		int len=SAM.GetLCP(x,l),br=z/BLOCK+1;
		r=l+len-1;
		base[i]=n-r;
		for(R j=0;j!=26;j++){
			auto T=lower_bound(Pos[j].begin(),Pos[j].end(),l);
			int cur=dis[j][z]+1+r-l;
			if(T==Pos[j].end()||*T>r){
				cur+=dis[j][l]<dis[j][r]?dis[j][l]:dis[j][r];
			}
			if(cur<ans[i]){
				ans[i]=cur;
			}
		}
		for(R j=z;j!=br*BLOCK&&j<=n;j++){
			int binL=1,binR=n-j+1,binM,res=0;
			while(binL<=binR){
				binM=binL+binR>>1;
				if(SAM.CheckSub(j,j+binM-1,l,r)==true){
					res=binM;
					binL=binM+1;
				}else{
					binR=binM-1;
				}
			}
			if(res!=0){
				int cur=(j<z?z-j:j-z)+r-l+2-res;
				if(cur<ans[i]){
					ans[i]=cur;
				}
			}
		}
		if(br*BLOCK<=n){
			Query A;
			A.Id=i;
			A.Left=l;
			A.Right=r;
			A.Target=z;
			Q[br].push_back(A);
		}
	}
	for(R i=n/BLOCK;i!=0;i--){
		SAM.DP(i*BLOCK);
		for(auto T:Q[i]){
			Min(ans[T.Id],T.Right-T.Left+2+SAM.GetDP(T.Left,T.Right)-T.Target);
		}
	}
	for(R i=0;i!=q;i++){
		printf("%d\n",ans[i]+base[i]);
	}
	return 0;
}
Asterisk Substrings

先建出SAM,算出原串本质不同子串个数。再建出后缀树,枚举通配符的位置,在两棵树上找到前缀后缀各自所在的节点,可以取到的串对为两个点分别跳失配点组成的串对。这可以转化为求子树的节点在另一棵树上对应的链的并的大小。这里启发式合并即可,用set维护dfs序。
时间复杂度 O ( n log ⁡ 2 2 n ) O(n \log_2^2n) O(nlog22n),空间复杂度 O ( n log ⁡ 2 n ) O(n \log_2n) O(nlog2n)

#include<stdio.h>
#include<string.h>
#include<vector>
#include<set>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 200001
char s[100002];
struct SAM{
	int Par[N],Len[N],Child[N][26],Pos[N],Tot,Last;
	I void Init(){
		Len[1]=Tot=Last=1;
	}
	I void Append(int x,int c){
		Tot++;
		int p=Last,cur=Tot;
		Pos[x]=Tot;
		Last=Tot;
		Len[Tot]=Len[p]+1;
		while(p!=0&&Child[p][c]==0){
			Child[p][c]=cur;
			p=Par[p];
		}
		if(p==0){
			Par[cur]=1;
		}else{
			int q=Child[p][c];
			if(Len[p]+1==Len[q]){
				Par[cur]=q;
			}else{
				Tot++;
				int nq=Tot;
				Len[nq]=Len[p]+1;
				for(R i=0;i!=26;i++){
					Child[nq][i]=Child[q][i];
				}
				Par[nq]=Par[q];
				Par[q]=Par[cur]=nq;
				while(Child[p][c]==q&&p!=0){
					Child[p][c]=nq;
					p=Par[p];
				}
			}
		}
	}
}Pre,Suf;
int h[N],sz[N],f[N],dep[N],p[N],val[N],Top[N],dfn[N];
vector<int>G[N];
I void PreDFS(int x,int&ct){
	ct++;
	dfn[x]=ct;
	p[ct]=x;
	sz[x]=1;
	for(int T:G[x]){
		dep[T]=dep[x]+1;
		PreDFS(T,ct);
		sz[x]+=sz[T];
		if(sz[T]>sz[h[x]]){
			h[x]=T;
		}
	}
}
I void ReDFS(int x,int t){
	Top[x]=t;
	if(h[x]!=0){
		ReDFS(h[x],t);
		for(int T:G[x]){
			if(T!=h[x]){
				ReDFS(T,T);
			}
		}
	}
}
I int LCA(int x,int y){
	while(Top[x]!=Top[y]){
		if(dep[Top[x]]>dep[Top[y]]){
			x=f[Top[x]];
		}else{
			y=f[Top[y]];
		}
	}
	return dep[x]<dep[y]?x:y;
}
I int Calc(int x,int y){
	return Suf.Len[LCA(x,y)];
}
set<int>S[N];
L sum[N];
I void Merge(set<int>&A,set<int>&B,L&sa,L&sb){
	if(A.size()<B.size()){
		A.swap(B);
		sa=sb;
	}
	for(int T:B){
		auto T0=A.insert(T).first;
		sa+=Suf.Len[p[*T0]];
		auto Tr=next(T0);
		if(T0!=A.begin()){
			auto Tl=prev(T0);
			if(Tr!=A.end()){
				sa+=Calc(p[*Tl],p[*Tr]);
			}
			sa-=Calc(p[*Tl],p[*T0]);
		}
		if(Tr!=A.end()){
			sa-=Calc(p[*T0],p[*Tr]);
		}
	}
	set<int>().swap(B); 
}
I void DFS(int x,L&ans){
	if(val[x]!=0){
		S[x].insert(dfn[val[x]]);
		sum[x]+=Suf.Len[val[x]];
	}
	for(int T:G[x]){
		DFS(T,ans);
		Merge(S[x],S[T],sum[x],sum[T]);
	}
	ans+=sum[x]*(Pre.Len[x]-Pre.Len[Pre.Par[x]]);
}
int main(){
	scanf("%s",s+1);
	int n=strlen(s+1),m;
	Pre.Init();
	Suf.Init();
	for(R i=1;i<=n;i++){
		Pre.Append(i,s[i]-'a');
	}
	for(R i=n;i!=0;i--){
		Suf.Append(i,s[i]-'a');
	}
	L ans=1;
	m=Suf.Tot;
	for(int i=2;i<=m;i++){
		f[i]=Suf.Par[i];
		G[f[i]].push_back(i);
		ans+=Suf.Len[i]-Suf.Len[Suf.Par[i]];
	}
	m=0;
	PreDFS(1,m);
	ReDFS(1,1);
	for(R i=1;i<=n;i++){
		val[i==1?1:Pre.Pos[i-1]]=i==n?1:Suf.Pos[i+1];
	}
	m=Pre.Tot;
	for(R i=1;i<=m;i++){
		vector<int>().swap(G[i]);
	}
	for(int i=2;i<=m;i++){
		G[Pre.Par[i]].push_back(i);
	}
	DFS(1,ans);
	printf("%lld",ans);
	return 0;
}
NOI2018 你的名字

将原串和询问串拼起来求后缀数组。对于一个询问串,先求出本质不同子串数量再减去不合法的即可。
先把询问串的后缀按后缀排名排序,算出本质不同子串个数。然后按原顺序求出每个后缀最长多长不合法。这个可以用类似后缀数组求height数组的求法,用主席树建议即可。
时空复杂度 O ( ( ∣ S ∣ + ∣ T ∣ ) log ⁡ 2 ( ∣ S ∣ + ∣ T ∣ ) ) O((|S|+|T|) \log_2(|S|+|T|)) O((S+T)log2(S+T))

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define R register int
#define I inline
#define Q 100000
#define N 1600001
#define M 9999999
char s[500001];
int a[N],Log2[N],sa[N],rk[N],hei[N],f[21][N],pos[Q],ql[Q],qr[Q],sl[Q],b[500000],ct;
I void BuildSA(const int n){
	for(R i=1;i<=n;i++){
		sa[i]=i;
	}
	std::sort(sa+1,sa+n+1,[](int x,int y){return a[x]<a[y];});
	static int ct[N],temrk[N],sa2[N];
	int sum=0,cur,*rk1=rk,*rk2=temrk;
	for(R i=1;i<=n;i++){
		if(a[sa[i]]!=a[sa[i-1]]){
			sum++;
		}
		rk[sa[i]]=sum;
	}
	for(R i=1;sum!=n;i<<=1){
		for(R j=0;j!=i;j++){
			sa2[j+1]=n-j;
		}
		cur=i;
		for(R j=1;j<=n;j++){
			if(sa[j]>i){
				cur++;
				sa2[cur]=sa[j]-i;
			}
		}
		for(R j=1;j<=sum;j++){
			ct[j]=0;
		}
		for(R j=1;j<=n;j++){
			ct[rk1[j]]++;
		}
		for(R j=1;j<=sum;j++){
			ct[j]+=ct[j-1];
		}
		for(R j=n;j!=0;j--){
			int&c=ct[rk1[sa2[j]]];
			sa[c]=sa2[j];
			c--;
		}
		sum=0;
		for(R j=1;j<=n;j++){
			if(rk1[sa[j]]!=rk1[sa[j-1]]||(sa[j]+i>n?0:rk1[sa[j]+i])!=(sa[j-1]+i>n?0:rk1[sa[j-1]+i])){
				sum++;
			}
			rk2[sa[j]]=sum;
		}
		int*t=rk1;
		rk1=rk2;
		rk2=t;
	}
	if(rk1!=rk){
		for(R i=1;i<=n;i++){
			rk[i]=rk1[i];
		}
	}
	cur=0;
	for(R i=1;i<=n;i++){
		if(cur!=0){
			cur--;
		}
		if(rk[i]!=1){
			int pos=sa[rk[i]-1];
			while(pos+cur<=n&&i+cur<=n&&a[pos+cur]==a[i+cur]){
				cur++;
			}
			hei[rk[i]]=cur;
		}
	}
}
I int GetLCP(int x,int y){
	x=rk[x];
	y=rk[y];
	if(x>y){
		int t=x;
		x=y;
		y=t;
	}
	int l=Log2[y-x];
	x=f[l][x];
	y=f[l][y-(1<<l)];
	return x<y?x:y;
}
class SegmentTree{
	int Root[N],Ls[M],Rs[M],Sum[M],Tot,Len;
	I void GetNode(int&x){
		Tot++;
		x=Tot;
	}
	I void Insert(int p1,int p2,int lf,int rt,const int x){
		Sum[p2]=Sum[p1]+1;
		if(lf!=rt){
			int mid=lf+rt>>1;
			if(x>mid){
				GetNode(Rs[p2]);
				Ls[p2]=Ls[p1];
				Insert(Rs[p1],Rs[p2],mid+1,rt,x);
			}else{
				GetNode(Ls[p2]);
				Rs[p2]=Rs[p1];
				Insert(Ls[p1],Ls[p2],lf,mid,x);
			}
		}
	}
	I bool Find(int p1,int p2,int lf,int rt,const int l,const int r){
		if(Sum[p2]==Sum[p1]){
			return false;
		}
		if(l<=lf&&rt<=r){
			return true;
		}
		int mid=lf+rt>>1;
		return l<=mid&&Find(Ls[p1],Ls[p2],lf,mid,l,r)||r>mid&&Find(Rs[p1],Rs[p2],mid+1,rt,l,r);
	}
	public:
		I void Init(const int n){
			Len=n;
		}
		I void Append(int pos,int x){
			if(x==0){
				Root[pos]=Root[pos-1];
			}else{
				GetNode(Root[pos]);
				Insert(Root[pos-1],Root[pos],1,Len,x);
			}
		}
		I bool Check(int lpos,int rpos,int x,int y){
			return Find(Root[lpos-1],Root[rpos],1,Len,x,y);
		}
}SEG;
int main(){
	scanf("%s",s);
	int n=strlen(s),len,m,q;
	for(R i=0;i!=n;i++){
		a[i+1]=s[i]-'a';
	}
	len=n;
	scanf("%d",&q);
	for(R i=0;i!=q;i++){
		scanf("%s",s);
		m=strlen(s);
		len++;
		a[len]=-1-i;
		pos[i]=len+1;
		sl[i]=m;
		for(R j=0;j!=m;j++){
			a[j+len+1]=s[j]-'a';
		}
		len+=m;
		scanf("%d%d",ql+i,qr+i);
	}
	BuildSA(len);
	for(R i=2;i<=len;i++){
		f[0][i-1]=hei[i];
		Log2[i]=Log2[i>>1]+1;
	}
	for(R i=1;1<<i<len;i++){
		for(R j=1;j<=len-(1<<i);j++){
			int x=f[i-1][j],y=f[i-1][j+(1<<i-1)];
			f[i][j]=x<y?x:y;
		}
	}
	SEG.Init(n);
	for(R i=1;i<=len;i++){
		SEG.Append(i,sa[i]>n?0:sa[i]);
	}
	for(R i=0;i!=q;i++){
		for(R j=0;j!=sl[i];j++){
			a[j]=pos[i]+j;
		}
		std::sort(a,a+sl[i],[](int x,int y){return rk[x]<rk[y];});
		long long ans=0;
		for(R j=0;j!=sl[i];j++){
			b[a[j]-pos[i]]=j==0?0:GetLCP(a[j-1],a[j]);
			ans+=pos[i]+sl[i]-a[j]-b[a[j]-pos[i]];
		}
		int cur=0;
		for(R j=0;j!=sl[i];j++){
			if(cur!=0){
				cur--;
			}
			while(cur<sl[i]&&cur<=qr[i]-ql[i]){
				int l,r;
				l=r=rk[j+pos[i]];
				for(R k=20;k!=-1;k--){
					if(l>1<<k&&f[k][l-(1<<k)]>cur){
						l-=1<<k;
					}
					if(r+(1<<k)<=len&&f[k][r]>cur){
						r+=1<<k;
					}
				}
				if(SEG.Check(l,r,ql[i],qr[i]-cur)==false){
					break;
				}
				cur++;
			}
			if(cur>b[j]){
				ans-=cur-b[j];
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值