UOJ #395. 【NOI2018】你的名字 后缀自动机

题意

给一个串S,每次询问给出一个串T和区间[l,r],问T中有多少个不同的子串满足其不是S[l…r]的子串。
∣ S ∣ , ∣ T ∣ ≤ 5 ∗ 1 0 5 , q ≤ 1 0 5 , ∑ ∣ T ∣ ≤ 1 0 6 |S|,|T|\le5*10^5,q\le10^5,\sum |T|\le10^6 S,T5105,q105,T106

分析

在noi考场上打了个又臭又长的做法,还只有68分。正解其实并不算难。
先补集转化一下,变成求T有多少个子串是S[l…r]的子串。
先对S建sam,然后每次对T也建一个sam。先对T的每一个前缀,求出一个最大的L使得该前缀的L后缀是S[l…r]的子串,那么T的sam上的某一个节点的贡献就很容易求了。
由于要对S的sam建可持久化线段树来维护right集,所以时间复杂度是 O ( ( ∣ S ∣ + ∑ ∣ T ∣ ) l o g n ) O((|S|+\sum |T|)logn) O((S+T)logn)

代码

#include
#include
#include
#include
#include

typedef long long LL;

const int N=500005;

int n,m,suf[N],b[N],c[N*2],tot;
char str[N];
struct tree{int l,r;}t[N*40];

void ins(int &d,int l,int r,int x)
{
	if (!d) d=++tot;
	if (l==r) return;
	int mid=(l+r)/2;
	if (x<=mid) ins(t[d].l,l,mid,x);
	else ins(t[d].r,mid+1,r,x);
}

int query(int d,int l,int r,int x)
{
	if (!d) return 0;
	if (l==r) return l;
	int mid=(l+r)/2,ans;
	if (x>mid&&(ans=query(t[d].r,mid+1,r,x))) return ans;
	else return query(t[d].l,l,mid,x);
}

int merge(int x,int y)
{
	if (!x||!y) return x^y;
	int z=++tot;
	t[z].l=merge(t[x].l,t[y].l);
	t[z].r=merge(t[x].r,t[y].r);
	return z;
}

struct Sam1
{
	int sz,last,ch[N*2][26],fa[N*2],mx[N*2],root[N*2];
	
	void extend(int x,int id)
	{
		int p,q,np,nq;
		p=last;last=np=++sz;mx[sz]=mx[p]+1;
		for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;
		if (!p) fa[np]=1;
		else
		{
			q=ch[p][x];
			if (mx[q]==mx[p]+1) fa[np]=q;
			else
			{
				nq=++sz;mx[nq]=mx[p]+1;
				memcpy(ch[nq],ch[q],sizeof(ch[q]));
				fa[nq]=fa[q];fa[q]=fa[np]=nq;
				for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
			}
		}
		ins(root[np],1,n,id);
	}
	
	void build()
	{
		memset(b,0,sizeof(b));
		for (int i=1;i<=sz;i++) b[mx[i]]++;
		for (int i=1;i<=n;i++) b[i]+=b[i-1];
		for (int i=1;i<=sz;i++) c[b[mx[i]]--]=i;
		for (int i=sz;i>1;i--) root[fa[c[i]]]=merge(root[c[i]],root[fa[c[i]]]);
	}
}Sam1;

struct Sam2
{
	int sz,last,ch[N*2][26],fa[N*2],mx[N*2],rig[N*2];
	
	void extend(int x,int id)
	{
		int p,q,np,nq;
		p=last;last=np=++sz;mx[np]=mx[p]+1;
		memset(ch[np],0,sizeof(ch[np]));
		for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;
		if (!p) fa[np]=1;
		else
		{
			q=ch[p][x];
			if (mx[q]==mx[p]+1) fa[np]=q;
			else
			{
				nq=++sz;mx[nq]=mx[p]+1;
				memcpy(ch[nq],ch[q],sizeof(ch[q]));
				fa[nq]=fa[q];fa[q]=fa[np]=nq;
				for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
			}
		}
		rig[np]=id;
	}
	
	void build()
	{
		for (int i=1;i<=m;i++) b[i]=0;
		for (int i=1;i<=sz;i++) b[mx[i]]++;
		for (int i=1;i<=m;i++) b[i]+=b[i-1];
		for (int i=1;i<=sz;i++) c[b[mx[i]]--]=i;
		for (int i=sz;i>1;i--) rig[fa[c[i]]]=rig[c[i]];
	}
}Sam2;

int main()
{
	scanf("%s",str+1);
	n=strlen(str+1);
	Sam1.last=Sam1.sz=1;
	for (int i=1;i<=n;i++) Sam1.extend(str[i]-'a',i);
	Sam1.build();
	int q;scanf("%d",&q);
	while (q--)
	{
		int l,r;
		scanf("%s%d%d",str+1,&l,&r);
		m=strlen(str+1);
		int now=1,len=0;
		Sam2.sz=Sam2.last=1;
		memset(Sam2.ch[1],0,sizeof(Sam2.ch[1]));
		for (int i=1;i<=m;i++)
		{
			int c=str[i]-'a';
			Sam2.extend(c,i);
			while (now&&!Sam1.ch[now][c]) now=Sam1.fa[now],len=Sam1.mx[now];
			if (!now) now=1;
			else now=Sam1.ch[now][c],len++;
			while (now)
			{
				int x=query(Sam1.root[now],1,n,r);
				if (!x||x-l+1<=Sam1.mx[Sam1.fa[now]]) now=Sam1.fa[now],len=Sam1.mx[now];
				else {len=std::min(len,x-l+1);break;}
			}
			suf[i]=len;
		}
		Sam2.build();
		LL ans=0;
		for (int i=2;i<=Sam2.sz;i++)
		{
			ans+=Sam2.mx[i]-Sam2.mx[Sam2.fa[i]];
			int x=Sam2.rig[i];
			ans-=std::max(0,std::min(suf[x],Sam2.mx[i])-Sam2.mx[Sam2.fa[i]]);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

你可能感兴趣的:(后缀自动机)