洛谷 P4770 [NOI2018]你的名字(后缀自动机+线段树合并)

题目链接

emmm,这题其实也不是特别难,个人感觉比之前做过的两道线段树合并right的后缀自动机都要简单不少(另两道都至少长2k……
首先考虑一下询问区间就是整串的情况
我们先对长串S建SAM
然后把询问串T在长串上跑匹配,假设 1 − T i 1-T_i 1Ti能匹配的长度为len,显然 i − l e n i-len ilen个串不会在 S S S中出现,我们把答案加上即可
但是吧,你仔细一看题,发现他要统计的是串的种类,要去重
那么的话可以考虑对 T T T再建一个后缀自动机
我们把每个i对应的 1 − T i 1-T_i 1Ti能匹配的长度 l e n len len放到 T T T的后缀自动机上对应串的节点上,显然一个点被覆盖的长度就是他所有子树里最长的 l e n len len,这样子我们把parent树建出来,在树上跑遍dfs,显然每个点的贡献就是这个点管辖的字符串数与他的最大长度减去被覆盖长度取min,加到答案上就可以了。
现在来考虑一下询问的是给定区间 l , r l,r l,r的情况
我们还是跑匹配,一般的匹配思路是看看有没有这条转移边,没有的话跳父亲
现在只用线段树合并一下right集合,匹配的时候看看区间里有没有这个转移后缀,没有的话把后缀长度减一再匹配,如果这个点最小长度都匹配不上就跳父亲,这样子就做完了
复杂度一只log

代码如下:

#include<bits/stdc++.h>
#define lson tr[now].l
#define rson tr[now].r
#define N 500050
using namespace std;

struct SM
{
	struct point
	{
		int len,fa,son[26];
	}t[N<<1],t1[N<<1];

	struct tree
	{
		int l,r,sum;
	}tr[N*60];

	int last=1,cnt=1,last1=1,cnt1=1,tot=0,len1,len2,rt[N<<1],pos[N],mx[N<<1];
	int ttt;
	long long ans=0;
	vector<int> g[N<<1],g1[N<<1];
	char s[500050],ss[500050];

	void push_up(int now)
	{
		tr[now].sum=tr[lson].sum|tr[rson].sum;
	}

	void insert(int &now,int l,int r,int pos)
	{
		if(!now) now=++tot;
		if(l==r)
		{
			tr[now].sum=1;
			return ;
		}
		int mid=(l+r)>>1;
		if(pos<=mid)
		{
			insert(lson,l,mid,pos);
		}
		else
		{
			insert(rson,mid+1,r,pos);
		}
		push_up(now);
	}

	int query(int now,int l,int r,int ll,int rr)
	{
		if(ll>rr) return 0;
		if(ll<=l&&r<=rr)
		{
			return tr[now].sum;
		}
		int mid=(l+r)>>1;
		if(rr<=mid)
		{
			return query(lson,l,mid,ll,rr);
		}
		else
		{
			if(mid<ll)
			{
				return query(rson,mid+1,r,ll,rr);
			}
			else
			{
				return query(lson,l,mid,ll,mid)|query(rson,mid+1,r,mid+1,rr);
			}
		}
	}

	int merge(int a,int b,int l,int r)
	{
		if(!a) return b;
		if(!b) return a;
		int now=++tot;
		if(l==r)
		{
			tr[now].sum=1;
			return now;
		}
		int mid=(l+r)>>1;
		tr[now].l=merge(tr[a].l,tr[b].l,l,mid);
		tr[now].r=merge(tr[a].r,tr[b].r,mid+1,r);
		push_up(now);
		return now;
	}

	void mem()
	{
		for(int i=1;i<=cnt1;i++)
		{
			memset(t1[i].son,0,sizeof(t1[i].son));
			g1[i].clear();
			mx[i]=0;
		}
		cnt1=1,last1=1;
	}

	void add(int c,int pos)
	{
		int p=last,np=++cnt;
		t[np].len=t[p].len+1;
		insert(rt[np],1,len1,pos);
		while(p&&(!t[p].son[c]))
		{
			t[p].son[c]=np;
			p=t[p].fa;
		}
		if(!p) t[np].fa=1;
		else
		{
			int q=t[p].son[c],nq;
			if(t[q].len==t[p].len+1)
			{
				t[np].fa=q;
			}
			else
			{
				nq=++cnt;
				t[nq]=t[q];
				t[nq].len=t[p].len+1;
				t[np].fa=t[q].fa=nq;
				while(p&&t[p].son[c]==q)
				{
					t[p].son[c]=nq;
					p=t[p].fa;
				}
			}
		}
		last=np;
	}

	void add1(int c)
	{
		int p=last1,np=++cnt1;
		t1[np].len=t1[p].len+1;
		while(p&&(!t1[p].son[c]))
		{
			t1[p].son[c]=np;
			p=t1[p].fa;
		}
		if(!p) t1[np].fa=1;
		else
		{
			int q=t1[p].son[c],nq;
			if(t1[q].len==t1[p].len+1)
			{
				t1[np].fa=q;
			}
			else
			{
				nq=++cnt1;
				t1[nq]=t1[q];
				t1[nq].len=t1[p].len+1;
				t1[np].fa=t1[q].fa=nq;
				while(p&&t1[p].son[c]==q)
				{
					t1[p].son[c]=nq;
					p=t1[p].fa;
				}
			}
		}
		last1=np;
	}

	void dfs1(int now)
	{
		for(int i=0;i<g[now].size();i++)
		{
			dfs1(g[now][i]);
			rt[now]=merge(rt[now],rt[g[now][i]],1,len1);
		}
	}

	void dfs2(int now,int fa)
	{
		for(int i=0;i<g1[now].size();i++)
		{
			dfs2(g1[now][i],now);
			mx[now]=mx[now]<mx[g1[now][i]]?mx[g1[now][i]]:mx[now];
		}
		int len=t1[now].len-t1[t1[now].fa].len;
		int tmp=t1[now].len-mx[now];
		if(tmp<0) tmp=0;
		ans+=tmp<len?tmp:len;
	}

	void solve()
	{
		int l,r;
		scanf("%s",s+1);
		len1=strlen(s+1);
		for(int i=1;i<=len1;i++) add(s[i]-'a',i);
		for(int i=1;i<=cnt;i++) g[t[i].fa].push_back(i);
		dfs1(1);
		scanf("%d",&ttt);
		while(ttt--)
		{
			ans=0;
			scanf("%s",ss+1);
			len2=strlen(ss+1);
			mem();
			for(int i=1;i<=len2;i++) add1(ss[i]-'a'),pos[i]=last1;
			for(int i=1;i<=cnt1;i++) g1[t1[i].fa].push_back(i);
			scanf("%d %d",&l,&r);
			int now=1,lenn=0;
			for(int i=1;i<=len2;i++)
			{
				int c=ss[i]-'a';
				lenn++;
				while(lenn)
				{
					if(query(rt[t[now].son[c]],1,len1,l+lenn-1,r))
					{
						break;
					}
					lenn--;
					if(lenn-1<=t[t[now].fa].len) now=t[now].fa;
				}
				if(lenn) now=t[now].son[c];
				else now=1;
				mx[pos[i]]=lenn;
			}
			dfs2(1,0);
			printf("%lld\n",ans);
		}
	}
}sam;

int main()
{
	sam.solve();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值