CF1073G Yet Another LCP Problem 题解
对于后缀 lcp 这种问题,先无脑对反串建个 SAM,然后 lcp 就变成后缀树上 lca 的 len 了。
考虑如果只有一次询问怎么做:对两个集合内的后缀所在结点打不同的标记,直接 dfs 一遍后缀树,维护子树内两种标记的数量,直接相乘再乘上 lenk−lenlinkk 即可。(这里不直接乘上 lcp 的长度 lenk 是因为这些贡献会在当前点到根的路径上每个点都被算一次,这样就不会算重。)
但是现在有多次询问,不能每次都 dfs,但是所有询问的集合总大小是有限制的。
俗话说:“见 ∑,想虚树。”
于是直接每次从后缀树上拉一颗虚树出来跑 dfs,就做完了。
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
using namespace std;
struct edge
{
int nxt,to;
}e[200001<<1];
int n,m,tot,h[200001<<1],cnt,lst,link[200001<<1],ch[200001<<1][26],len[200001<<1],s[200001<<1][2],fa[200001<<1][21],dep[200001<<1],dfn[200001<<1],id,num[200001],top,t[200001],q[200001<<1];
long long ans;
string str;
inline int read()
{
int x=0;
char c=getchar();
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x;
}
inline bool cmp(int x,int y)
{
return dfn[x]<dfn[y];
}
inline void add(int x,int y)
{
e[++tot].nxt=h[x];
h[x]=tot;
e[tot].to=y;
}
inline void build(int c)
{
int cur=++cnt,p=lst;
len[cur]=len[lst]+1;
lst=cur;
for(;~p;p=link[p])
if(!ch[p][c])
ch[p][c]=cur;
else
break;
if(p==-1)
return;
int q=ch[p][c];
if(len[p]+1==len[q])
{
link[cur]=q;
return;
}
int clone=++cnt;
link[clone]=link[q];
len[clone]=len[p]+1;
for(int i=0;i<26;++i)
ch[clone][i]=ch[q][i];
link[cur]=link[q]=clone;
for(;~p;p=link[p])
if(ch[p][c]==q)
ch[p][c]=clone;
else
break;
}
inline void dfs1(int k,int f,int deep)
{
dep[k]=deep;
fa[k][0]=f;
dfn[k]=++id;
for(int i=1;i<=20;++i)
fa[k][i]=fa[fa[k][i-1]][i-1];
for(int i=h[k];i;i=e[i].nxt)
dfs1(e[i].to,k,deep+1);
}
inline int LCA(int x,int y)
{
if(dep[x]<dep[y])
x^=y^=x^=y;
for(int d=dep[x]-dep[y],i=0;1<<i<=d;++i)
if(d&(1<<i))
x=fa[x][i];
if(x==y)
return x;
for(int i=20;~i;--i)
if(fa[x][i]^fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
return fa[x][0];
}
inline void dfs2(int k,int f)
{
for(int i=h[k];i;i=e[i].nxt)
{
dfs2(e[i].to,k);
s[k][0]+=s[e[i].to][0];
s[k][1]+=s[e[i].to][1];
}
if(k)
ans+=1ll*s[k][0]*s[k][1]*(len[k]-len[f]);
}
inline void dfs3(int k)
{
for(int i=h[k];i;i=e[i].nxt)
dfs3(e[i].to);
h[k]=s[k][0]=s[k][1]=0;
}
int main()
{
n=read(),m=read();
cin>>str;
link[0]=-1;
for(int i=n-1;~i;--i)
{
build(str[i]-'a');
num[i+1]=lst;
}
for(int i=1;i<=cnt;++i)
{
add(link[i],i);
//cout<<link[i]<<" "<<i<<'\n';
}
dfs1(0,-1,1);
dfs3(0);
tot=0;
while(m--)
{
int p1=read(),p2=read();
t[top=1]=0;
for(int i=1;i<=p1;++i)
++s[q[i]=num[read()]][0];
for(int i=1;i<=p2;++i)
++s[q[i+p1]=num[read()]][1];
sort(q+1,q+p1+p2+1,cmp);
q[0]=0;
for(int i=1;i<=p1+p2;++i)
{
if(q[i]==q[i-1])
continue;
int lca=LCA(t[top],q[i]);
if(lca!=t[top])
{
while(dfn[lca]<dfn[t[top-1]])
{
add(t[top-1],t[top]);
--top;
}
if(lca!=t[top-1])
{
add(lca,t[top]);
t[top]=lca;
}
else
add(lca,t[top--]);
}
t[++top]=q[i];
}
while(top>1)
{
add(t[top-1],t[top]);
--top;
}
ans=0;
dfs2(0,0);
cout<<ans<<'\n';
dfs3(0);
tot=0;
}
return 0;
}
峰峰峰の妙妙屋
怄火。挥手。转圈。街舞。跳跳。献吻。跳绳。激动。发抖。磕头。爱情。飞吻。左太极。右太极。回头。