树上启发式合并

dsu on tree.

树上启发式合并

对于一个点 \(u\),需要计算其子树内的答案。
先遍历 \(u\) 的轻子树并计算其答案,计算答案过程中会统计一些信息,不保留其信息。轻子树计算完后进入重子树计算答案,并保留其信息。
然后对于以 \(u\) 为根的子树,再次遍历其轻子树并加入答案。若 \(u\) 是其父节点的重儿子,则保留信息,否则不保留。

以上就是 dsu on tree,因为重子树大小的性质,每棵轻子树最多会再遍历 \(\log n\) 次,所以时间复杂度为 \(\mathcal{O(n \log n \cdot k)}\)。其中 \(k\) 是统计答案的时间复杂度。
dsu on tree 可以很好地处理有关子树内问题或者是全局统计满足某种条件的某种答案的问题。

例题

CF741D.

通过分析可知当 \(popcount(a_u \oplus a_v) \le 1\) 时便满足条件,令当前统计的子树根为 \(u\),考虑记录一个 \(f_s\) 表示 \(a_i\) 值为 \(s\) 时的最大深度,答案需要从这几方面统计:

  1. \(u\) 子树内答案;
  2. \(u\) 的重儿子及其子树和 \(u\) 匹配的答案;
  3. \(u\) 的子树与子树之间的答案,需要每计算完一个子树和它之前子树匹配的答案,再将这棵子树的信息加入。
  4. \(u\) 与其轻子树内节点匹配的答案。
#include <bits/stdc++.h>

// #define int long long
#define ll long long
#define ull unsigned long long
#define db double
#define ld long double
#define rep(i,l,r) for (int i = (int)(l); i <= (int)(r); ++ i )
#define rep1(i,l,r) for (int i = (int)(l); i >= (int)(r); -- i )
#define il inline
#define fst first
#define snd second
#define ptc putchar
#define Yes ptc('Y'),ptc('e'),ptc('s'),puts("")
#define No ptc('N'),ptc('o'),puts("")
#define YES ptc('Y'),ptc('E'),ptc('S'),puts("")
#define NO ptc('N'),ptc('O'),puts("")
#define vi vector<int>
#define pb emplace_back
#define sz(x) (int)(x.size())
#define all(x) x.begin(),x.end()
#define me(a,x) memset(a,x,sizeof a)
#define get(x) ((x - 1) / len + 1)
#define debug() puts("------------")

using namespace std;
typedef pair<int,int> PII;
typedef pair<int,PII> PIII;
typedef pair<ll,ll> PLL;
namespace szhqwq {
    template<class T> il void read(T &x) {
        x = 0; T f = 1; char ch = getchar();
        while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
        while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar(); }
        x *= f;
    }
    template<class T,class... Args> il void read(T &x,Args &...x_) { read(x); read(x_...); }
    template<class T> il void print(T x) {
        if (x < 0) ptc('-'), x = -x; 
        if (x > 9) print(x / 10); ptc(x % 10 + '0');
    }
    template<class T,class T_> il void write(T x,T_ ch) { print(x); ptc(ch); }
    template<class T,class T_> il void chmax(T &x,T_ y) { x = x < (T)y ? (T)y : x; }
    template<class T,class T_> il void chmin(T &x,T_ y) { x = x > (T)y ? (T)y : x; }
    template<class T,class T_,class T__> il T qmi(T a,T_ b,T__ p) {
        T res = 1; while (b) {
            if (b & 1) res = res * a % p;
            a = a * a % p; b >>= 1;
        } return res;
    }
    template<class T> il T gcd(T a,T b) { if (!b) return a; return gcd(b,a % b); }
    template<class T,class T_> il void exgcd(T a, T b, T_ &x, T_ &y) {
        if (b == 0) { x = 1; y = 0; return; }
        exgcd(b,a % b,y,x); y -= a / b * x; return ;
    }
    template<class T,class T_> il T getinv(T x,T_ p) { 
        T inv,y; exgcd(x,(T)p,inv,y);
        inv = (inv + p) % p; return inv;
    }
} using namespace szhqwq;
const int N = 1e6 + 10,inf = 1e9,mod = 998244353;
const ull base = 131,base_ = 233;
const ll inff = 1e18;
const db eps = 1e-6;
int n,val[N],h[N],e[N << 1],ne[N << 1],idx,a[N];
int f[1 << 23],son[N],siz[N],d[N],ans[N],mx;

il void add(int a,int b) {
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++;
    return ;
}

il void dfs(int u,int fa) {
    a[u] = a[fa] ^ val[u];
    d[u] = d[fa] + 1;
    siz[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue;
        dfs(j,u);
        siz[u] += siz[j];
        if (siz[j] > siz[son[u]]) son[u] = j;
    }
    return ;
}

il void calc(int u,int fa,int rt) {
    if (!(a[u] ^ a[rt])) chmax(mx,d[u] - d[rt]);
    rep(p,0,21) if ((a[u] ^ a[rt]) == (1 << p)) chmax(mx,d[u] - d[rt]);
    if (f[a[u]]) chmax(mx,f[a[u]] + d[u] - 2 * d[rt]);
    rep(p,0,21) if (f[a[u] ^ (1 << p)]) chmax(mx,f[a[u] ^ (1 << p)] + d[u] - 2 * d[rt]);
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue;
        calc(j,u,rt);
    }
    return ;
}

il void change(int u,int fa,bool fl) {
    if (fl) chmax(f[a[u]],d[u]);
    else f[a[u]] = 0;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue;
        change(j,u,fl);
    }
    return ;
}

il void dsu(int u,int fa,bool fl) {
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa || j == son[u]) continue;
        dsu(j,u,0);
        chmax(ans[u],ans[j]);
    }
    if (son[u]) dsu(son[u],u,1),chmax(mx,ans[son[u]]);
    if (f[a[u]]) chmax(mx,f[a[u]] - d[u]);
    rep(p,0,21) if (f[a[u] ^ (1 << p)]) chmax(mx,f[a[u] ^ (1 << p)] - d[u]);
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa || j == son[u]) continue;
        calc(j,u,u);
        change(j,u,1);
    }
    chmax(f[a[u]],d[u]);
    chmax(ans[u],mx);
    if (!fl) mx = 0,change(u,fa,0);
    return ;
}

il void solve() {
    //------------code------------
    read(n); me(h,-1);
    rep(i,2,n) {
        int p; read(p);
        char c; cin >> c;
        val[i] = (1 << c - 'a');
        add(i,p); add(p,i);
    }
    dfs(1,0); dsu(1,0,1);
    rep(i,1,n) write(ans[i],' ');
    return ;
}

il void init() {
    return ;
}

signed main() {
    // init();
    int _ = 1;
    // read(_);
    while (_ -- ) solve();
    return 0;
}

板子

il void dfs(int u,int fa) {
    // 预处理
    d[u] = d[fa] + 1;
    siz[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue;
        dfs(j,u);
        siz[u] += siz[j];
        if (siz[j] > siz[son[u]]) son[u] = j;
    }
    return ;
}

il void calc(int u,int fa,int rt) {
    // 计算轻子树间 & 轻子树与 rt 间的答案
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue;
        calc(j,u,rt);
    }
    return ;
}

il void change(int u,int fa,bool fl) {
    // 修改信息
    if (fl) // add
    else // del
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue;
        change(j,u,fl);
    }
    return ;
}

il void dsu(int u,int fa,bool fl) {
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa || j == son[u]) continue;
        dsu(j,u,0);
    }
    if (son[u]) dsu(son[u],u,1);
    // 计算重子树与子树根节点答案
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa || j == son[u]) continue;
        calc(j,u,u);
        change(j,u,1);
    }
    // 加入 u 节点信息
    // 统计 u 为根子树的答案
    if (!fl) mx = 0,change(u,fa,0);
    return ;
}
posted @ 2025-03-15 14:32  songszh  阅读(27)  评论(0)    收藏  举报