[BZOJ3879]SvT(后缀树+虚树)
题面
有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n].
现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始位置来表示),求这些后缀两两之间的LCP的长度之和.一对后缀之间的LCP长度仅统计一遍.
分析
建出S的后缀树(实现上直接建反串SAM的parent树). 每一组询问的后缀相当于后缀树上的一个点集。两个后缀在后缀树上对应的节点LCA的len就是他们的LCP长度。因此我们要查询这些点两两之间LCA的len之和。
把这些关键点建出虚树。然后在虚树上做类似树形dp的统计。设(sz_x)表示(x)当前已经合并的子树中关键点的个数,则对于还未被合并的儿子(y),它和已经合并的子树会产生(sz_x cdot sz_y)对点,而这些点对的LCA都是(x)。因此对答案的贡献为(sz_x cdot sz_y cdot len(x))
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define maxc 26
#define maxn 3000000
#define maxm 3000000
#define mod 23333333333333333ll
using namespace std;
template<typename T> void qread(T &x){
x=0;
T sign=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') sign=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=x*10+c-'0';
c=getchar();
}
x=x*sign;
}
template<typename T> void qprint(T x){
if(x<0){
putchar('-');
qprint(-x);
}else if(x==0){
putchar('0');
return;
}else{
if(x>=10) qprint(x/10);
putchar('0'+x%10);
}
}
int n,m;
char str[maxn+5];
int pos[maxn+5];
vector<int>E[maxn+5];
int qu,q[maxm+5];
struct SAM {
#define len(x) (t[x].len)
#define link(x) (t[x].link)
struct node {
int ch[maxc];
int link;
int len;
} t[maxn+5];
const int root=1;
int ptr=1;
int last=root;
int extend(int c) {
int p=last,cur=++ptr;
len(cur)=len(p)+1;
while(p&&t[p].ch[c]==0) {
t[p].ch[c]=cur;
p=link(p);
}
if(p==0) link(cur)=root;
else {
int q=t[p].ch[c];
if(len(p)+1==len(q)) link(cur)=q;
else {
int clo=++ptr;
t[clo]=t[q];
len(clo)=len(p)+1;
link(q)=link(cur)=clo;
while(p&&t[p].ch[c]==q) {
t[p].ch[c]=clo;
p=link(p);
}
}
}
last=cur;
return cur;
}
void build(char *s){
int len=strlen(s+1);
for(int i=len;i>=1;i--) pos[i]=extend(s[i]-'a');
for(int i=1;i<=ptr;i++) E[link(i)].push_back(i);
#ifdef DEBUG
puts("tree:");
for(int i=1;i<=ptr;i++) printf("%d %d
",link(i),i);
printf("pos:");
for(int i=1;i<=len;i++) printf("%d ",pos[i]);
printf("
");
#endif
}
}S;
int tim;
int dfn[maxm+5],deep[maxm+5],top[maxm+5],son[maxm+5],fa[maxm+5],sz[maxm+5];
void dfs1(int x,int f){
sz[x]=1;
fa[x]=f;
deep[x]=deep[f]+1;
for(int i=0;i<(int)E[x].size();i++){
int y=E[x][i];
if(y!=f){
dfs1(y,x);
sz[x]+=sz[y];
if(sz[y]>sz[son[x]]) son[x]=y;
}
}
}
void dfs2(int x,int t){
dfn[x]=++tim;
top[x]=t;
if(son[x]) dfs2(son[x],t);
for(int i=0;i<(int)E[x].size();i++){
int y=E[x][i];
if(y!=fa[x]&&y!=son[x]) dfs2(y,y);
}
}
int lca(int x,int y){
while(top[x]!=top[y]){
if(deep[top[x]]<deep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return deep[x]<deep[y]?x:y;
}
vector<int>vt[maxm+5];//虚树
bool is_orig[maxm+5];//虚树中的关键点(不含lca)
int stk[maxm+5],_top;
inline int cmp(int x,int y){
return dfn[x]<dfn[y];
}
void vt_insert(int x){
if(_top<1){
stk[++_top]=x;
return;
}
int lc=lca(x,stk[_top]);
if(stk[_top]==lc){
stk[++_top]=x;
return;
}
while(_top>1&&deep[stk[_top-1]]>=deep[lc]){
vt[stk[_top-1]].push_back(stk[_top]);
_top--;
}
if(stk[_top]!=lc) vt[lc].push_back(stk[_top]);
stk[_top]=lc;
stk[++_top]=x;
}
long long ans=0;
int dfs3(int x,int f){
int szx=0;//子树中关键点个数,只开一个变量,这样不用清空
if(is_orig[x]) szx++;
for(int i=0;i<(int)vt[x].size();i++){
int y=vt[x][i];
if(y!=f){
int szy=dfs3(y,x);
ans=(ans+1ll*szx*szy*S.t[x].len%mod)%mod;
szx+=szy;
}
}
vt[x].clear();
return szx;
}
void get_tree(int *p,int cnt){
sort(p+1,p+1+cnt,cmp);
cnt=unique(p+1,p+1+cnt)-p-1;//题目说可能有重复后缀
_top=0;
for(int i=1;i<=cnt;i++) is_orig[p[i]]=1;
int root=0;
for(int i=1;i<=cnt;i++){
if(!root) root=p[i];
else root=lca(root,p[i]);
}
for(int i=1;i<=cnt;i++) vt_insert(p[i]);
while(_top>1){
vt[stk[_top-1]].push_back(stk[_top]);
_top--;
}
ans=0;
dfs3(root,0);
for(int i=1;i<=cnt;i++) is_orig[p[i]]=0;
}
int main(){
qread(n);
qread(m);
scanf("%s",str+1);
S.build(str);
dfs1(1,0);
dfs2(1,1);
for(int i=1;i<=m;i++){
qread(qu);
for(int j=1;j<=qu;j++){
qread(q[j]);
q[j]=pos[q[j]];
}
get_tree(q,qu);
qprint(ans);
putchar('
');
}
}
/*
4 3
aaaa
2 3 4
*/