P4770 [NOI2018] 你的名字(SAM上容斥维护endpos)

LINK

Case1

先考虑一个弱化版本,也就是每次询问区间是 l = 1 & & r = ∣ S ∣ l=1\&\&r=|S| l=1&&r=S

怎么求串 T T T拥有和 S S S不同的本质不同串数量??

考虑对 T T T的每一个前缀 T [ 1... i ] T[1...i] T[1...i]求出一个 r e s [ i ] res[i] res[i]表示和串 S S S的最长公共后缀

这个对 S S S建一个 S A M SAM SAM在上面跑就好了

然后 ∑ r e s [ i ] \sum\limits res[i] res[i]包含了很多重复的子串,去掉这些重复的子串,剩下的就是本质不同且和 S S S相同的子串

T T T也建一个 S A M SAM SAM,考虑自动机上的节点 k k k

节点 k k k上有 l [ k ] − l [ f a k ] l[k]-l[fa_k] l[k]l[fak]种子串,其中有多少在串 S S S中出现过呢??

我们从节点 k k k取出 e n d p o s endpos endpos集合中的任何一个结束位置 m i k mi_k mik(代码中直接取第一个,因为好写)

说明 T [ m i k − r e s [ m i k ] + 1 , m i k ] T[mi_k-res[mi_k]+1,mi_k] T[mikres[mik]+1,mik]的所有后缀在 S S S中出现过

k k k节点包含 T [ m i k − l [ f a k ] , m i k ] , T [ m i k − l [ f a k ] − 1 , m i k ] . . . T [ m i k − l [ k ] + 1 , m i i ] T[mi_k-l[fa_k],mi_k],T[mi_k-l[fa_k]-1,mi_k]...T[mi_k-l[k]+1,mi_i] T[mikl[fak],mik],T[mikl[fak]1,mik]...T[mikl[k]+1,mii]这些串

减去这部分重复的就好了

所以答案是

∑ k ∈ S A M t m a x ( 0 , l [ k ] − m a x ( l [ f a k ] , r e s [ m i k ] ) ) \sum\limits_{k\in SAM_t}max(0,l[k]-max(l[fa_k],res[mi_k])) kSAMtmax(0,l[k]max(l[fak],res[mik]))

Case 2

考虑每次询问 S S S的区间任意怎么写

实际上就是在用 S S S的自动机匹配 T T T时不能有边就转移了

有边,还需要考虑一下当前匹配的长度为 l e n len len的串是否在区间 [ L , R ] [L,R] [L,R]

实际上就是判断下一个节点是否存在 [ L + l e n − 1 , R ] [L+len-1,R] [L+len1,R] e n d p o s endpos endpos

线段树合并维护 e n d p o s endpos endpos即可

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10+2e5+10;
const int N = 1e6*22;
//下面是线段树 
int sum[N],rt[maxn],ls[N],rs[N],Line,n;
void push_up(int r)
{
	sum[r] = sum[ls[r]] + sum[rs[r]];	
}
void add(int &root,int l,int r,int index)
{
	if( l>index || r<index )	return;
	if( !root )	root = ++Line;
	if( l==r&&l==index ){ sum[root]=1; return; }
	int mid = l+r>>1;
	add( ls[root],l,mid,index); add( rs[root],mid+1,r,index );
	push_up( root );
}
int ask(int &root,int l,int r,int L,int R)
{
	if( l>R || r<L )	return 0;
	if( root==0 )	return 0;
	if( l>=L&&r<=R )	return sum[root];
	int mid = l+r>>1;
	return ask( ls[root],l,mid,L,R)+ask( rs[root],mid+1,r,L,R );
}
int merge(int x,int y,int l,int r)
{
	if( !x || !y )	return x|y;
	int p = ++Line;
	if( l==r ){ sum[p]=sum[x] | sum[y]; return p; }
	int mid = l+r>>1;
	ls[p] = merge( ls[x],ls[y],l,mid );
	rs[p] = merge( rs[x],rs[y],mid+1,r );
	push_up( p );
	return p;
}

char a[maxn];
int res[maxn];
struct SAM
{
	int zi[maxn][27],fa[maxn],l[maxn],mi[maxn],las = 1, id = 1;
	void insert(int c,int ok)
	{
		int p = las, np = ++id; las = id;
		mi[np] = l[np] = l[p]+1;
		if( ok )	add( rt[np],1,n,l[np] );
		for( ;p&&zi[p][c]==0;p=fa[p] )		zi[p][c] = np;
		if( !p )	fa[np] = 1;
		else
		{
			int q = zi[p][c];
			if( l[q] == l[p]+1 )	fa[np] = q;
			else
			{
				int nq=++id;
				memcpy( zi[nq],zi[q],sizeof zi[nq] );
				l[nq] = l[p]+1, fa[nq] = fa[q]; mi[nq] = mi[q];
				fa[np] = fa[q] = nq;
				for( ;p&&zi[p][c]==q;p=fa[p] )	zi[p][c] = nq;
			}
		}
	}
	long long solve()
	{
		long long ans = 0;
		for(int i=1;i<=id;i++)	ans += max( 0,l[i]-max( l[fa[i]],res[mi[i]] ) );
		return ans;
	}
	void init()
	{
		for(int i=1;i<=id;i++)
		{
			memset( zi[i],0,sizeof zi[i] );
			fa[i] = l[i] = mi[i] = 0;
		}
		las = id = 1;
	}
}s1,s2;
vector<int>vec[maxn];
void dfs(int u,int father)//树上倍增 
{
	for(auto v:vec[u] )
	{
		dfs( v,u );
		rt[u] = merge( rt[u],rt[v],1,n );
	}
}
void build()
{
	scanf("%s",a+1 ); n = strlen( a+1 );
	for(int i=1;i<=n;i++)	s1.insert( a[i]-'a',1 ); 
	for(int i=2;i<=s1.id;i++)	vec[s1.fa[i]].push_back( i );
	dfs(1,0);//线段树合并 
}
int main()
{
	build();
	int q; scanf("%d",&q);
	while( q-- )
	{
		int L,R,p=1;
		scanf("%s%d%d",a+1,&L,&R ); int le = strlen( a+1 );
		for(int i=1;i<=le;i++)
		{
			res[i] = res[i-1];
			while( 1 )//一直去匹配即可 
			{
				int v = s1.zi[p][a[i]-'a'];
				if( v&&ask( rt[v],1,n,L+res[i],R) )	{ p = v; res[i]++; break; }
				if( !res[i] ) {  p=1 ; break; }
				res[i]--;
				if( res[i]==s1.l[s1.fa[p]] )	p = s1.fa[p];
			}
			s2.insert( a[i]-'a',0 );
		}
		printf("%lld\n",s2.solve() );
		s2.init();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值