P9399 「DBOI」Round 1 人生如树 题解

前言

好典呀~,一眼看出做法,但细节好烦人,还好很快调出来了,跑得很快。

前置知识\(\operatorname{LCA}\)(最近公共祖先),倍增,字符串哈希,二分。

解析

说下我第一眼的想法:发现这个 \(\operatorname{LRP}\)\(\operatorname{LCP}\)(最长公共前缀)极其相似,只是加了一个偏移量,考虑使用字符串哈希 + 二分求这个东西。至于操作二,加入一个叶子对原树没有影响,单独处理即可。

由于字符串在树上,所以这是一个很屎的链上二分。

然后来看一看我们要做的准备工作:

  1. \(\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];
}

  1. 树上 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;
}
  1. 预处理哈希要用的信息。

这里用了楼顶大佬的模数 \(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\) 的求法,思考一下就可以知道:

\[h1_u=h1_{fa}\times B+w_u \]

\[h2_u=h2_{fa} + mi_{dep_u-1}\times w_u \]

这里取根的 \(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\) 的链长度。

  1. \(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'}};\)

  1. \(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;
}
posted @ 2026-03-24 12:38  TP2010  阅读(6)  评论(0)    收藏  举报