题解 CF1073G 【Yet Another LCP Problem】

万弘

2020-08-09 14:46:04

Solution

## CF1073G 题意:求$\sum_i^{|A|}\sum_j^{|B|}\operatorname{LCP}(\operatorname{suf}(a_i),\operatorname{suf}(b_j))$. $\operatorname{suf}(x)$表示$[x...n]$这个后缀. 首先两后缀的 LCP 即为其对应终止节点在后缀树上的 LCA 的状态,那么显然有 LCP 的长度即为后缀树上 LCA 的深度(表示的状态的长度) 那么问题转化为,给两个点集 $A,B,$求 $$ \sum_{a\in A}\sum_{b\in B}\operatorname{dep}(\operatorname{LCA}(a,b)) $$ 这就很裸了吧,就是 [GXOI/GZOI2019]旧词 那题的套路。对于每个 $b$,将其到根的路径上每个点的点权+1,$a$ 的贡献就转化为到根的路径上点权和。直接 LCT/树剖维护就好了。 两个小细节: 1. 可以用 SAM建后缀树。 2. 后缀树是经过压缩的,LCA的深度并不直接是根到他的距离,而应是其表示的状态的长度。 同时那些被压掉的点也是有贡献的,但这些点没有必要真的去修改点权,直接加到下面连的没有被压掉的点上就好了,就变成链上每个点加定值,询问链和,用树剖+线段树/LCT维护。 PS:其实用 LCT 写会短很多~~但我写挂了~~ ```cpp #define MAXN 400011 struct SAM { int t[MAXN][26],pre[MAXN],len[MAXN]; int last,tot; SAM(){tot=last=1;} void insert(int w) { int pos=last,cur=++tot; len[cur]=len[pos]+1,last=cur; while(pos&&!t[pos][w])t[pos][w]=cur,pos=pre[pos]; if(!pos){pre[cur]=1;return;} int nxt=t[pos][w]; if(len[nxt]==len[pos]+1)pre[cur]=nxt; else { int tmp=++tot; len[tmp]=len[pos]+1; memcpy(t[tmp],t[nxt],sizeof t[nxt]); pre[tmp]=pre[nxt],pre[nxt]=pre[cur]=tmp; while(pos&&t[pos][w]==nxt)t[pos][w]=tmp,pos=pre[pos]; } } }sam; struct Segment_Tree { struct node { ll sum,k,tag; }t[MAXN<<2|1]; #define rt t[num] #define tl t[num<<1] #define tr t[num<<1|1] void pushdown(un l,un r,un num) { if(!rt.tag)return; un mid=(l+r)>>1; tl.sum+=tl.k*rt.tag,tl.tag+=rt.tag; tr.sum+=tr.k*rt.tag,tr.tag+=rt.tag; rt.tag=0; } void build(int* arr,un l=1,un r=sam.tot,un num=1) { if(l==r)rt.k=arr[l]; else { un mid=(l+r)>>1; build(arr,l,mid,num<<1),build(arr,mid+1,r,num<<1|1); rt.k=tl.k+tr.k; } } void modify(un ql,un qr,ll k,un l=1,un r=sam.tot,un num=1) { if(ql<=l&&r<=qr){rt.sum+=k*rt.k,rt.tag+=k;return;} pushdown(l,r,num); un mid=(l+r)>>1; if(ql<=mid)modify(ql,qr,k,l,mid,num<<1); if(qr>mid)modify(ql,qr,k,mid+1,r,num<<1|1); rt.sum=tl.sum+tr.sum; } ll Qsum(un ql,un qr,un l=1,un r=sam.tot,un num=1) { if(ql<=l&&r<=qr)return rt.sum; pushdown(l,r,num); un mid=(l+r)>>1;ll ans=0; if(ql<=mid)ans+=Qsum(ql,qr,l,mid,num<<1); if(qr>mid)ans+=Qsum(ql,qr,mid+1,r,num<<1|1); return ans; } }sgt; struct edge{int v,nxt;}e[MAXN]; int cnt=0,last[MAXN]; void adde(int u,int v){e[++cnt].v=v,e[cnt].nxt=last[u],last[u]=cnt;} int size[MAXN],fa[MAXN],mson[MAXN]; void dfs1(int u) { size[u]=1; for(int i=last[u];i;i=e[i].nxt) { int v=e[i].v; fa[v]=u,dfs1(v); if(size[v]>size[mson[u]])mson[u]=v; size[u]+=size[v]; } } int top[MAXN],dfn[MAXN],w[MAXN], cur=0; void dfs2(int u,int now_top) { top[u]=now_top,dfn[u]=++cur,w[cur]=sam.len[u]-sam.len[fa[u]]; if(mson[u])dfs2(mson[u],now_top); for(int i=last[u];i;i=e[i].nxt) if(e[i].v!=mson[u])dfs2(e[i].v,e[i].v); } void add_to_root(int u,int k) { while(u)sgt.modify(dfn[top[u]],dfn[u],k),u=fa[top[u]]; } ll Qsum_to_root(int u) { ll res=0; while(u)res+=sgt.Qsum(dfn[top[u]],dfn[u]),u=fa[top[u]]; return res; } int ed[MAXN],a[MAXN],b[MAXN]; char s[MAXN]; int main() { int n=read(),m=read();scanf("%s",s+1); for(int i=n;i;--i)sam.insert(s[i]-'a'),ed[i]=sam.last;//建反串SAM for(int i=2;i<=sam.tot;++i)adde(sam.pre[i],i);//建后缀树 dfs1(1),dfs2(1,1),sgt.build(w); while(m--) { int k=read(),l=read(); ll ans=0; for(int i=1;i<=k;++i)a[i]=read(); for(int i=1;i<=l;++i)b[i]=read(),add_to_root(ed[b[i]],1); for(int i=1;i<=k;++i)ans+=Qsum_to_root(ed[a[i]]); for(int i=1;i<=l;++i)add_to_root(ed[b[i]],-1); printf("%lld\n",ans); } return 0; } ```