bzoj 3879 SvT

20 篇文章 0 订阅
1 篇文章 0 订阅

3879: SvT

Time Limit: 30 Sec  Memory Limit: 512 MB
Submit: 764  Solved: 297
[ Submit][ Status][ Discuss]

Description

(我并不想告诉你题目名字是什么鬼)

有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n].

现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始位置来表示),求这些后缀两两之间的LCP(LongestCommonPrefix)的长度之和.一对后缀之间的LCP长度仅统计一遍.

Input

第一行两个正整数n,m,分别表示S的长度以及询问的次数.

接下来一行有一个字符串S.

接下来有m组询问,对于每一组询问,均按照以下格式在一行内给出:

首先是一个整数t,表示共有多少个后缀.接下来t个整数分别表示t个后缀在字符串S中的出现位置.

Output

对于每一组询问,输出一行一个整数,表示该组询问的答案.由于答案可能很大,仅需要输出这个答案对于23333333333333333(一个巨大的质数)取模的余数.

Sample Input

7 3

popoqqq

1 4

2 3 5

4 1 2 5 6

Sample Output


0

0

2

Hint

样例解释:

对于询问一,只有一个后缀”oqqq”,因此答案为0.

对于询问二,有两个后缀”poqqq”以及”qqq”,两个后缀之间的LCP为0,因此答案为0.

对于询问三,有四个后缀”popoqqq”,”opoqqq”,”qqq”,”qq”,其中只有”qqq”,”qq”两个后缀之间的LCP不为0,且长度为2,因此答案为2.

对于100%的测试数据,有S<=5*10^5,且Σt<=3*10^6.

特别注意:由于另一世界线的某些参数发生了变化,对于一组询问,即使一个后缀出现了多次,也仅算一次.

HINT

Source

By YGY






【分析】

非常难受。非常难受。虚树非常恶心。

建议没做过 bzoj 3238 差异 的同学先去搞一波事情...当然要写SAM版本的...

首先把问题转化为:求两两前缀的LCS之和,那么把串翻一下,搞出来parent树,事实上就是搞出来了反串的前缀树= =

博主是傻逼!明明是原串的后缀树!哪来的前缀树!!!

性质:SAM中两个状态x,y的LCP=step[LCA(x,y)]。(不知道的同学去看 张天扬 后缀自动机的论文)

和bzoj 3238 一样,做一下树形dp。

但是非常坑爹多组询问,那么我们把每组询问的关键点拿出来建一棵虚树,在虚树上DP,会省很多时间= =(不会虚树的同学找度娘)

然后注意初始化不能用memset(TLE到飞起),好像也不能搜一遍虚树来初始化(爆栈,可能是我写的比较水),在dfs一遍DP 的时候DP结束就把子节点清零掉。

20000ms+卡过去。不得不说我真的自带大常数。




【代码】

//bzoj 3879 SvT
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
#define M(a) memset(a,0,sizeof a)
#define fo(i,j,k) for(i=j;i<=k;i++)
using namespace std;
const int mxn=2000005;
long long ans;
char s[mxn>>1],t[mxn>>1];
int Q,n,m,p,q,np,nq,len,tot,root,tim;
int dep[mxn],pos[mxn],fa[mxn>>1][27],st[mxn],mark[mxn];
int step[mxn],son[mxn>>1][26],pre[mxn],tmp[3*mxn],dfn[mxn];
inline bool comp(int x,int y) {return dfn[x]<dfn[y];}
struct tree
{
	int cnt;
	int head[mxn];ll mark[mxn];
	struct edge {int to,next;} f[mxn];
	inline void add(int u,int v)
	{
		f[++cnt].to=v,f[cnt].next=head[u],head[u]=cnt;
	}
}R,F;   //REAL & FAKE
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return x*f;
}
inline void sam(int w,int c)
{
	p=np;
	step[np=(++tot)]=step[p]+1;
	pos[w]=np;
	while(p && !son[p][c])
	  son[p][c]=np,p=pre[p];
	if(!p) {pre[np]=root;return;}
	q=son[p][c];
	if(step[q]==step[p]+1) {pre[np]=q;return;}
	step[nq=(++tot)]=step[p]+1;
	memcpy(son[nq],son[q],sizeof son[q]);
	pre[nq]=pre[q];
	pre[np]=pre[q]=nq;
	while(p && son[p][c]==q)
	  son[p][c]=nq,p=pre[p];
}
inline void down(int u)
{
	dfn[u]=++tim;
	for(int i=R.head[u];i;i=R.f[i].next)
	  dep[R.f[i].to]=dep[u]+1,down(R.f[i].to);
}
inline void init()
{
	int i,j;
	fo(i,1,tot) fa[i][0]=pre[i],R.add(pre[i],i);
	fo(j,1,20)
	  fo(i,1,tot)
	    fa[i][j]=fa[fa[i][j-1]][j-1];
	dep[1]=1,down(1);
}
inline int lca(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	for(int j=20;j>=0;j--)
	  if(dep[fa[x][j]]>=dep[y]) x=fa[x][j];
	if(x==y) return x;
	for(int j=20;j>=0;j--)
	  if(fa[x][j]!=fa[y][j]) x=fa[x][j],y=fa[y][j];
	return fa[x][0];
}
inline void dfs(int u)
{
	for(int i=F.head[u];i;i=F.f[i].next)
	{
		int v=F.f[i].to;
		dfs(v);
		ans+=(ll)step[u]*mark[u]*mark[v];
		mark[u]+=mark[v];mark[v]=0;
	}
	F.head[u]=0;
}
inline void fake()   //构建(有向)虚树 
{
	F.cnt=0;
	int i,j,top=0;
	sort(tmp+1,tmp+m+1,comp);
	m=unique(tmp+1,tmp+m+1)-tmp-1;
	fo(i,1,m) mark[tmp[i]]=1;
	fo(i,1,m)
	{
		if(!top) {st[++top]=tmp[i];continue;}
		int u=lca(tmp[i],st[top]);
		while(top && dep[u]<dep[st[top]])
		{
			if(dep[u]>=dep[st[top-1]])
			{
				F.add(u,st[top]);
				if(st[--top]!=u) st[++top]=u;
				break;
			}
			top--,F.add(st[top],st[top+1]);
		}
		st[++top]=tmp[i];
	}
//	fo(i,1,top) printf("test=%d\n",st[i]);
	fo(i,1,top-1) F.add(st[i],st[i+1]);
	ans=0;dfs(st[1]);printf("%lld\n",ans);
	mark[st[1]]=F.head[st[1]]=0;
}
int main()
{
	int i,j;
	np=tot=root=1;
	len=read(),Q=read();
	scanf("%s",t+1);
	fo(i,1,len) s[i]=t[len-i+1];
	fo(i,1,len) sam(i,s[i]-'a');
	init();
	while(Q--)
	{
		m=read();
		fo(i,1,m)
		  tmp[i]=pos[len-read()+1];
		fake();
//		printf("!!!\n");
	}
	return 0;
}
/*
7 1
popoqqq
4 1 2 5 6
*/


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值