【NOI2018】你的名字

后缀自动机+主席树

为这种精妙的字符串题调上一天真的是件很幸福的事呢!

题意转化过来之后就变成:给一个串S,有若干个询问,每次询问给字符串T,整数 L,整数 R,询问有多少个子串k使得k是T的子串但不是S[L]....S[R]的子串

1.首先我们考虑这个问题的弱化版本,每次询问的L≡1,R≡len(S)     ——68pts

对于T的每个[ 1 , i ]的前缀子串,我们假设其后缀在S中能匹配到的最长子串的长度为maxlen,那么我们知道在T的后缀自动机的 i 对应的这个节点上所能表示的串的长度在len[fa[x]]~len[x]之间,那么对应 i 这个节点所不能表示出来的子串的个数就是len[x]-max(len[fa[x]],maxlen),因为这个子串可能完全匹配上了,所以我们还要对0取个max,因为完全匹配上之后应该在下一个节点处考虑这个前缀而非当前,因为当前串不可能在产生失配的贡献。然后我们将T的后缀自动机建出来,在parent树上跑一跑,累加一下答案就ok了。

那么maxlen怎么求呢?

我们考虑[1 , i ]的maxlen已经算出为now,怎么求[1 , i+1 ],此时假设在S的后缀自动机上匹配到p号点,那么我们看看p号点有没有连出去的s[i+1]字符的边 并且这个边所连向的点的endpos里面是否有>now的这个位置,如果有那[1 , i+1 ]的答案就是now+1了。如果没有连着的s[i+1]的这条边,那不用说,肯定是匹配不上的,但是如果有,那也不一定能匹配上!(注意这里很重要) 为什么呢?因为我们知道后缀自动机的节点其实代表的是每种类型的endpos,就算连出去有s[i+1]的边,但是它通向字符的位置(endpos)可能是在[1,now-1]之前的,这样显然是不合法的,因为你T串匹配上了now个,而最后一个字符匹配上S的位置却小于now,显然是不可行的。

那么不可行之后,如果now为0了 那我们应该直接break 因为无论如何之后都会失配了,如果now还是大于0,那么就让now--,因为[1 , i ] 后缀最长匹配到now,而[1 , i ]+s[ i ] 必然是只能从now-1开始匹配的,不然的话maxlen为now的时候一定能匹配上!

至此 我们已经掌握了68pts的写法

2.现在我们来考虑一下100分的写法

注意到前面我们考虑T从[1 , i]转移到[1 , i+1]时的那两个判断的条件1.“有连向s[i+1]的边”且2.“连向的那个点的endpos里面有在范围里的 ”,1条件不用说,很简单,让我们来想想2条件。在简化版的问题中,合法范围显然就是1~n。那么对于S中L~R这段子串来说呢?合法范围显然就是[ L , R ]。那么我们只需要额外考虑怎么判断该节点的endpos里是否有在合法范围里的,其它的做法与上面完全一致!

那么我们怎么来求出后缀自动机的某个节点上的endpos的值具体有哪些呢?

我们可以对每个点都建一个线段树来维护这个节点上的endpos中有无 i,有的话在线段树上标 1,反之标 0。然后考虑在parent树上,父亲的endpos是所有儿子的endpos的并,所以我们可以考虑在dfs序上建主席树,那么查询每个节点的endpos中是否有在范围里面,则可以查询主席树[dfn[x]-1,dfn[x]+si[x]-1]这段区间里差分线段树(这就是所有儿孙的线段树的并)中在你查询的合法区间内是否有值,若有,则可行,另之相反。(这里好像也可以直接将儿子的线段树合并到父亲的线段树上去,我一开始这么写的,拿了92分,mle了2个节点,可能是我合并姿势错了qwq)

好啦,现在你已经成功掌握了100分的做法了!

时间效率:O(nlogn)

空间效率:nlogn

代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define lch a[n].lc
#define rch a[n].rc
using namespace std;
const int maxn=1e6+10;
int T,n,len;
char s[maxn];
struct suda
{
	struct daa{int ch[27],fa,len;}po[maxn],po2[maxn];
	struct da{int lc,rc,si;}a[30*maxn];
	int las,tot,las1,tot1,st[maxn],to[maxn],nt[maxn],topt,cnt,root[maxn],lc[maxn];
	int dfn[maxn],line[maxn],si[maxn],dfn_num;
	int ma[maxn],maxlen[maxn];
	long long ans;
	void init1() 
	{
		memset(st,0,sizeof st); 
		memset(root,0,sizeof root);
		memset(a,0,sizeof a);
		memset(po,0,sizeof po);
		memset(ma,0,sizeof ma);
		memset(maxlen,0,sizeof maxlen);
		memset(lc,0,sizeof lc);
		cnt=0; topt=0; las=tot=1;
	}
	void init2() 
	{
		for (int i=0;i<=2*len+1;i++) 
		{
			st[i]=0;
			for (int j=0;j<26;j++) po2[i].ch[j]=0;
			po2[i].fa=0; po2[i].len=0;
		}
		topt=0; las1=tot1=1;
	}
	void insert1(int x,int id)
	{
		int p=las,np=las=++tot; po[np].len=po[p].len+1;
		lc[np]=id;
		for (;p&&(!po[p].ch[x]);p=po[p].fa) po[p].ch[x]=np;
		if (!p) po[np].fa=1;
		else
		{
			int q=po[p].ch[x];
			if (po[q].len==po[p].len+1) po[np].fa=q;
			else
			{
				int nq=++tot; po[nq]=po[q];
				po[nq].len=po[p].len+1;
				po[q].fa=po[np].fa=nq;
				for (;p&&(po[p].ch[x]==q);p=po[p].fa) po[p].ch[x]=nq;
			}
		}
	}
	void add(int x,int y)
	{
		to[++topt]=y; nt[topt]=st[x]; st[x]=topt;
	}
	void updata(int n){a[n].si=a[lch].si+a[rch].si;}
	void build_tree(int &n,int l,int r)
	{
		n=++cnt; if (l==r) {a[n].si=0; return;}
		int mid=(l+r)>>1;
		build_tree(lch,l,mid); build_tree(rch,mid+1,r);
		updata(n);
	}
	void tree_add(int &n,int old,int l,int r,int lc)
	{
		n=++cnt;
		if (l==r) {a[n].si=a[old].si+1; return;}
		int mid=(l+r)>>1;
		if (lc<=mid) {rch=a[old].rc; tree_add(lch,a[old].lc,l,mid,lc);}
		else {lch=a[old].lc; tree_add(rch,a[old].rc,mid+1,r,lc);}
		updata(n);
	}
	int qury(int n,int L,int R,int l,int r)
	{
		if (l>r) return 0;
		if (L==l && R==r) return a[n].si;
		int mid=(L+R)>>1;
		if (r<=mid) return qury(lch,L,mid,l,r);
		else if (l>=mid+1) return qury(rch,mid+1,R,l,r);
		else return qury(lch,L,mid,l,mid)+qury(rch,mid+1,R,mid+1,r);
	}
	void dfs(int x)
	{
		dfn[x]=++dfn_num; line[dfn_num]=x;
		if (lc[x])
		{
			tree_add(root[dfn[x]],root[dfn[x]-1],1,n,lc[x]);
		}else root[dfn[x]]=root[dfn[x]-1];
		int p=st[x]; si[x]=1;
		while (p)
		{			
			dfs(to[p]); si[x]+=si[to[p]];
			p=nt[p];
		}
		//printf("kkqqqq id=%d si=%d treesize=%d\n",x,sii[x],a[root[x]].si);
	}
	void prework()
	{
		//printf("lalalalala %d\n",po[1].ch[18]);
		for (int i=2;i<=tot;i++) 
		{
			add(po[i].fa,i);
			//printf("edge: %d->%d\n",po[i].fa,i);
		}
		build_tree(root[0],1,n); 
		dfs(1);
	}
	void getma(int l,int r)
	{
		for (int i=0;i<=2*len+10;i++) ma[i]=maxlen[i]=0;
		int p=1,now=0;
		for (int i=1;i<=len;i++)
		{
			int x=s[i]-'a';
			//printf("kkkkq %d %d %d\n",x,po[p].ch[x],qury(root[po[p].ch[x]],1,n,l+now,r));
			//printf("%d:\n",i);
			while (1)
			{
				//printf("%d ",p);
				if (po[p].ch[x] && (qury(root[dfn[po[p].ch[x]]+si[po[p].ch[x]]-1],1,n,l+now,r)-qury(root[dfn[po[p].ch[x]]-1],1,n,l+now,r)>0))
				{
					p=po[p].ch[x]; now++; break;
				}
				if (!now) break;
				now--;
				if (now==po[po[p].fa].len) p=po[p].fa;
			}
			//printf("\n");
			maxlen[i]=now;
			//printf("%d %d\n",i,maxlen[i]);
		}
	}
	void insert2(int x,int id)
	{
		int p=las1,np=las1=++tot1; po2[np].len=po2[p].len+1;
		ma[np]=maxlen[id]; 
		//printf("kqkqkqqlala %d %d %d\n",np,maxlen[id],id);
		for (;p&&(!po2[p].ch[x]);p=po2[p].fa) po2[p].ch[x]=np;
		if (!p) po2[np].fa=1;
		else
		{
			int q=po2[p].ch[x];
			if (po2[q].len==po2[p].len+1) po2[np].fa=q;
			else 
			{
				int nq=++tot1; po2[nq]=po2[q];
				po2[nq].len=po2[p].len+1;
				po2[q].fa=po2[np].fa=nq;
				for (;p&&(po2[p].ch[x]==q);p=po2[p].fa) po2[p].ch[x]=nq;
			}
		}
	}
	void calc(int x)
	{
		int p=st[x];
		while (p)
		{
			calc(to[p]);
			ma[x]=max(ma[x],ma[to[p]]);
			p=nt[p];
		}
		//printf("kkqq %d %d\n",x,ma[x]);
		ans+=max(0,po2[x].len-max(ma[x],po2[po2[x].fa].len));
	}
	long long solve()
	{
		ans=0;
		for (int i=2;i<=tot1;i++) add(po2[i].fa,i);
		calc(1);
	return ans;
	}
}SAM;
int main()
{
	scanf("%s",s+1); n=strlen(s+1); 
	SAM.init1();
	for (int i=1;i<=n;i++) SAM.insert1(s[i]-'a',i);
	SAM.prework();
	scanf("%d",&T);
	while (T--)
	{
		scanf("%s",s+1); len=strlen(s+1);
		int l,r; scanf("%d%d",&l,&r);
		SAM.getma(l,r); SAM.init2();
		for (int i=1;i<=len;i++) SAM.insert2(s[i]-'a',i);
		printf("%lld\n",SAM.solve());
	}
return 0;
}
/*
scbamgepe
1
smape 2 7
*/

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值