3879: SvT
Time Limit: 30 Sec Memory Limit: 512 MB
Submit: 764 Solved: 297
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
【分析】
非常难受。非常难受。虚树非常恶心。
建议没做过 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
*/