P9399 「DBOI」Round 1 人生如树 题解
前言
好典呀~,一眼看出做法,但细节好烦人,还好很快调出来了,跑得很快。
前置知识:\(\operatorname{LCA}\)(最近公共祖先),倍增,字符串哈希,二分。
解析
说下我第一眼的想法:发现这个 \(\operatorname{LRP}\) 和 \(\operatorname{LCP}\)(最长公共前缀)极其相似,只是加了一个偏移量,考虑使用字符串哈希 + 二分求这个东西。至于操作二,加入一个叶子对原树没有影响,单独处理即可。
由于字符串在树上,所以这是一个很屎的链上二分。
然后来看一看我们要做的准备工作:
- 求 \(\operatorname{LCA}\) 来确定链的顶端,把链分成两端,来求一条链的哈希值。我采用的倍增求 \(\operatorname{LCA}\)。
void dfs(int u,int ff){//预处理信息
fa[0][u]=ff,dep[u]=dep[ff]+1;
for (int i=1;(1<<i)<=dep[u];i++) fa[i][u]=fa[i-1][fa[i-1][u]];
for (int v:e[u]) if (v!=ff) dfs(v,u);
}
inline int LCA(int x,int y){//倍增求LCA
if (dep[x]<dep[y]) swap(x,y);
for (int i=LG;i>=0;i--)
if (dep[x]-(1<<i)>=dep[y]) x=fa[i][x];
if (x==y) return x;
for (int i=LG;i>=0;i--)
if (fa[i][x]!=fa[i][y]) x=fa[i][x],y=fa[i][y];
return fa[0][x];
}
- 树上 K 级祖先,用来在二分时确定链的末尾,用来求链的哈希值。用倍增求即可。
inline int KA(int x,int k){//树上 K 级祖先
for (int i=LG;i>=0;i--)
if ((k>>i)&1) x=fa[i][x];
return x;
}
- 预处理哈希要用的信息。
这里用了楼顶大佬的模数 \(mod=167772161\) 和基数 \(B=13331\)。
我们把一条链分为了两段,要通过合并两段信息来获得链的哈希值。
发现从深度来看,第一段是从下到上排列的,第二段是从上到下排列的,所以我们考虑求每个点 \(u\) 到根的字符串的哈希值 \(h1_u\),和从根到每个点 \(u\) 的字符串的哈希值 \(h2_u\),通多差分来获取每一段的哈希值。
在两段对接时,需要 \(mi_i=B^i\) 和 \(inv_i=(\frac{1}{B})^i\) 来平移字符串,给出逆元 \(INV=68274246\)。
别忘了 \(\operatorname{LRP}\) 是有一个偏移量的,记 \(h0_i\) 为字符串 \(123456\dots i\) 的哈希值,到时候直接加上即可转化为 \(\operatorname{LCP}\) 问题。
inline void init(){
mi[0]=1,inv[0]=1;
for (int i=1;i<=n+m;i++) {
h0[i]=(1ll*h0[i-1]*B+i)%mod;
mi[i]=1ll*mi[i-1]*B%mod;
inv[i]=1ll*inv[i-1]*INV%mod;
}
}
对于 \(h1,h2\) 的求法,思考一下就可以知道:
这里取根的 \(dep\) 为 1。
void dfs(int u,int ff){//预处理信息
fa[0][u]=ff,dep[u]=dep[ff]+1;
h1[u]=(h1[ff]+1ll*mi[dep[u]-1]*w[u])%mod;
h2[u]=(1ll*h2[ff]*B+w[u])%mod;
for (int i=1;(1<<i)<=dep[u];i++) fa[i][u]=fa[i-1][fa[i-1][u]];
for (int v:e[u]) if (v!=ff) dfs(v,u);
}
然后就可以开始二分了。
我们二分答案为 \(mid\),即对应链长为 \(mid\)。
对于 \(u1,v1,u2,v2\),考虑如何求其中的一条链的哈希值,记 \(lca\) 为 \(u,v\) 的 \(\operatorname{LCA}\),\(flca=fa_{lca}\),\(p\) 为 链的末尾,记 \(len\) 为 \(u\) 到 \(v\) 的链长度。
- 若 \(p\) 在 \([u,lca]\) 上,即 \(mid\le dep_u-dep_{lca}+1\)。
则只有一段连续的链 \([u,p]\)。
易得:\(p\) 为 \(u\) 的第 \(mid-1\) 级祖先,计算会用到的 \(p'\) 为第 \(mid\) 级祖先。
则哈希值 \(H=(h1_u-h1_{p'})\times inv_{dep_{p'}};\)。
- 若 \(p\) 在 \((lca,v]\) 上,即 \(mid > dep_u-dep_{lca}+1\)。
分为 \([u,lca]\) 和 \((lca,p]\)。
易得:\(p\) 为 \(v\) 的第 \(len-mid\) 级祖先。
其中 \([u,lca]\) 的哈希值为 \((h1_u-h1_{flca})\times inv_{dep_{flca}}\),记为 \(H1\)。
其中 \((lca,v]\) 的哈希值为 \(h2_p-h2_{lca}\times mi_{dep_p-dep_{lca}}\),记为 \(H2\)。
则 \(H=H1\times mi_{mid-(dep_u-dep_{lca}+1)}+H2\)。
最后二分时判一下两条链的 \(H\) 是否相等即可。
时间复杂度 \(O(m\log^2n)\),但其实跑的飞快,可能是我的常数较小。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define gc() (rp1==rp2&&(rp2=(rp1=buf)+fread(buf,1,1<<22,stdin))==rp1?EOF:*rp1++)
char buf[1<<22],*rp1,*rp2;
inline int read(){
int d=0,f=0;char ch=gc();
while (!isdigit(ch)) f|=(ch=='-'),ch=gc();
while (isdigit(ch)) d=(d<<1)+(d<<3)+(ch^48),ch=gc();
return f?-d:d;
}
inline void write(int n){
int stk[30],tp=0;
do{stk[++tp]=n%10;n/=10;}while(n);
while (tp) putchar(stk[tp--]+'0');
putchar('\n');
}
const int N=200050,mod=167772161,B=13331,INV=68274246;
int n,m,idx,w[N],LG;
vector <int> e[N];
int dep[N],fa[20][N],h1[N],h2[N],h0[N],mi[N],inv[N];
//h1自下而上
//h2自上而下
void dfs(int u,int ff){//预处理信息
fa[0][u]=ff,dep[u]=dep[ff]+1;
h1[u]=(h1[ff]+1ll*mi[dep[u]-1]*w[u])%mod;
h2[u]=(1ll*h2[ff]*B+w[u])%mod;
for (int i=1;(1<<i)<=dep[u];i++) fa[i][u]=fa[i-1][fa[i-1][u]];
for (int v:e[u]) if (v!=ff) dfs(v,u);
}
inline void init(){
mi[0]=1,inv[0]=1;
for (int i=1;i<=n+m;i++) {
h0[i]=(1ll*h0[i-1]*B+i)%mod;
mi[i]=1ll*mi[i-1]*B%mod;
inv[i]=1ll*inv[i-1]*INV%mod;
}
}
inline int LCA(int x,int y){//倍增求LCA
if (dep[x]<dep[y]) swap(x,y);
for (int i=LG;i>=0;i--)
if (dep[x]-(1<<i)>=dep[y]) x=fa[i][x];
if (x==y) return x;
for (int i=LG;i>=0;i--)
if (fa[i][x]!=fa[i][y]) x=fa[i][x],y=fa[i][y];
return fa[0][x];
}
inline int KA(int x,int k){//树上 K 级祖先
for (int i=LG;i>=0;i--)
if ((k>>i)&1) x=fa[i][x];
return x;
}
int main(){
n=read(),m=read(),idx=read(),LG=__lg(n+m)+1;
init();
for (int i=1;i<=n;i++) w[i]=read();
for (int i=1;i<n;i++){
int u=read(),v=read();
e[u].emplace_back(v),e[v].emplace_back(u);
}
dfs(1,0);
while (m--){
int opt=read();
if (opt==1){
int u1=read(),v1=read(),lca1=LCA(u1,v1);
int len1=dep[u1]+dep[v1]-dep[lca1]*2+1,l1=dep[u1]-dep[lca1]+1;
int u2=read(),v2=read(),lca2=LCA(u2,v2);
int len2=dep[u2]+dep[v2]-dep[lca2]*2+1,l2=dep[u2]-dep[lca2]+1;
int l=1,r=min(len1,len2),ans=0;
while (l<=r){
int mid=l+r>>1,H1,H2;
if (mid<=l1) {
int p=KA(u1,mid);
H1=1ll*(h1[u1]-h1[p]+mod)*inv[dep[p]]%mod;
}
else {
int p=KA(v1,len1-mid);
H1=1ll*(h1[u1]-h1[fa[0][lca1]]+mod)*inv[dep[fa[0][lca1]]]%mod;
H1=(1ll*H1*mi[mid-l1]+h2[p]-1ll*h2[lca1]*mi[dep[p]-dep[lca1]]%mod+mod)%mod;
}
if (mid<=l2) {
int p=KA(u2,mid);
H2=1ll*(h1[u2]-h1[p]+mod)*inv[dep[p]]%mod;
}
else {
int p=KA(v2,len2-mid);
H2=1ll*(h1[u2]-h1[fa[0][lca2]]+mod)*inv[dep[fa[0][lca2]]]%mod;
H2=(1ll*H2*mi[mid-l2]+h2[p]-1ll*h2[lca2]*mi[dep[p]-dep[lca2]]%mod+mod)%mod;
}
if ((H1+h0[mid])%mod==H2) l=mid+1,ans=mid;
else r=mid-1;
}
write(ans);
}
else {
int u=read();w[++n]=read();
dep[n]=dep[u]+1;fa[0][n]=u;
for (int i=1;(1<<i)<=dep[u];i++) fa[i][n]=fa[i-1][fa[i-1][n]];
h1[n]=(h1[u]+1ll*mi[dep[n]-1]*w[n])%mod;
h2[n]=(1ll*h2[u]*B+w[n])%mod;
}
}
return 0;
}

浙公网安备 33010602011771号