uoj 395. 【NOI2018】你的名字

题意:

给出一个模式串 S S S,多次询问询问串有多少本质不同的子串在 s [ l , r ] s[l,r] s[l,r]中没有出现。

题解:

这题好像不是很难
首先68分比较sb,对于每个 T T T的前缀,只要能求出最短的还没有在 T T T中出现过的,和最短的还没有在 S S S中出现过的后缀,就能计算这个前缀的贡献。
转为求最长的出现过的,就是sam经典操作。
现在加上了 l , r l,r l,r这个限制,影响的只有在 S S S中的的部分。那么用可持久化线段树维护right,你看下当前节点是否存在合法的位置。
有就不用管了,没有的话,求出在当前节点 l , r l,r l,r范围最大的值,那么可以得到一个新的当前节点匹配长度,当然还要判断这个长度是否需要跳 f a i l fail fail
具体代码,挺好写的,
code:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#define LL long long
using namespace std;
struct trnode{
	int lc,rc,c;
}tr[45000010];int tot=0,root[1000010];
struct node{
	int y,next;
}a[1000010];int len=0,last[1000010];
void ins(int x,int y)
{
	a[++len].y=y;
	a[len].next=last[x];last[x]=len;
}
void update(int &x,int l,int r,int k)
{
	if(!x) x=++tot;
	if(l==r) {tr[x].c++;return;}
	int mid=(l+r)/2;
	if(k<=mid) update(tr[x].lc,l,mid,k);
	else update(tr[x].rc,mid+1,r,k);
	tr[x].c=tr[tr[x].lc].c+tr[tr[x].rc].c;
}
int solve(int x,int l,int r)
{
	if(!tr[x].c) return 0;
	if(l==r) return l;
	int mid=(l+r)/2;
	if(tr[tr[x].rc].c) return solve(tr[x].rc,mid+1,r);
	return solve(tr[x].lc,l,mid);
}
int findans(int x,int l,int r,int fl,int fr)
{
	if(!x||l>r) return 0;
	if(l==fl&&r==fr) return solve(x,l,r);
	int mid=(l+r)/2;
	if(fr<=mid) return findans(tr[x].lc,l,mid,fl,fr);
	if(fl>mid) return findans(tr[x].rc,mid+1,r,fl,fr);
	int t=findans(tr[x].rc,mid+1,r,mid+1,fr);
	return t?t:findans(tr[x].lc,l,mid,fl,mid);
}
int n,Q,N;
struct SAM{
	int rt,tot,tail;
	struct node{
		int a[26],max,par;
	}sam[2000010];
	void node() {tot=0;}
	void init()
	{
		for(int i=1;i<=tot;i++)
		{
			sam[i].max=sam[i].par=0;
			memset(sam[i].a,0,sizeof(sam[i].a));
		}
		rt=tot=tail=1;
	}
	void add(int c,int op)
	{
		int p=tail,np=++tot;
		sam[np].max=sam[p].max+1;
		if(op) update(root[np],1,N,sam[np].max);
		for(;p&&!sam[p].a[c];p=sam[p].par) sam[p].a[c]=np;
		tail=np;
		if(!p) sam[np].par=rt;
		else
		{
			int q=sam[p].a[c];
			if(sam[q].max==sam[p].max+1) sam[np].par=q;
			else
			{
				int nq=++tot;sam[nq]=sam[q];
				sam[nq].max=sam[p].max+1;
				sam[np].par=sam[q].par=nq;
				for(;p&&sam[p].a[c]==q;p=sam[p].par) sam[p].a[c]=nq;
			}
		}
	}
}s1,s2;
char s[1000010];
int merge(int x1,int x2,int l,int r)
{
	if(!x1||!x2) return x1+x2;
	int x=++tot;tr[x]=tr[x1];
	if(l==r) tr[x].c=tr[x1].c+tr[x2].c;
	else
	{
		int mid=(l+r)/2;
		tr[x].lc=merge(tr[x1].lc,tr[x2].lc,l,mid);
		tr[x].rc=merge(tr[x1].rc,tr[x2].rc,mid+1,r);
		tr[x].c=tr[tr[x].lc].c+tr[tr[x].rc].c;
	}
	return x;
}
void dfs(int x)
{
	for(int i=last[x];i;i=a[i].next)
	{
		int y=a[i].y;dfs(y);
		root[x]=merge(root[x],root[y],1,N);
	}
}
int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	s1.init();N=2*n;
	for(int i=1;i<=n;i++) s1.add(s[i]-'a',1);
	for(int i=2;i<=s1.tot;i++) ins(s1.sam[i].par,i);
	dfs(1);
	scanf("%d",&Q);
	while(Q--)
	{
		scanf("%s",s+1);
		int l,r;scanf("%d %d",&l,&r);
		n=strlen(s+1);
		s2.init();
		int x=1,len=0;LL ans=0;
		for(int i=1;i<=n;i++)
		{
			int c=s[i]-'a';
			s2.add(c,0);
			while(x!=1&&!s1.sam[x].a[c]) x=s1.sam[x].par,len=s1.sam[x].max;
			if(s1.sam[x].a[c]) x=s1.sam[x].a[c],len++;
			while(x!=1&&!findans(root[x],1,N,l+len-1,r))
			{
				int k=findans(root[x],1,N,l,r);
				int Len=min(len,k-l+1);
				if(s1.sam[s1.sam[x].par].max<Len) {len=Len;break;}
				x=s1.sam[x].par;len=s1.sam[x].max;
			}
			ans+=i-max(len,s2.sam[s2.sam[s2.tail].par].max);
		}
		printf("%lld\n",ans);
	}
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值