loj2553. 「CTSC2018」暴力写挂

题意

给定两棵树\(T, T'\),求
\[ \max_{(x, y)} dep_x + dep_y - (dep_{lca(x, y)} + dep'_{lca'(x, y)}) \]

题解

经过一些化简后就变成了求
\[ \max_{(x, y)} \frac{1}{2} dep_x + \frac{1}{2} dep_y + \frac{1}{2} dis(x, y) - dep'_{lca'(x, y)} \]
有两种主流做法。
一种是边分+虚树+dp。
这种还没实现过。
另一种是边分+点分树合并。
考虑对第一棵树进行边分,设\(d_x\)\(x\)到当前边分中心的距离,则求
\[ \max_{(x, y)} \frac{1}{2} (dep_x + d_x) + \frac{1}{2} (dep_y + d_x) - dep'_{lca'(x, y)} \]
在一次边分中,前半部分是容易求得的。
考虑固定后面部分,即枚举\(x, y\)在第二棵树上的lca。
枚举了lca,要保证\(x, y\)在lca的子树中,怎么办呢?
考虑用对于第二棵树的每一个点建一类棵动态开点线段树,每棵这种树维护在第一棵树中经过该点在不同的点分子树中的贡献(要记录在不同的点分中心的左右两侧的产生的贡献,贡献的形式就是\(dep_x + d_x\)),然后在第二棵树上从下往上合并即可,合并的时候对于两个点在同一棵点分子树的同一个部分(左/右)的贡献取max。并且左边和左边合并,右边和右边合并,左边只能和右边产生贡献,右边只能和左边产生贡献,就和线段树合并类似了。至于复杂度,大概就是根据边分树的优越结构的性质(完全二叉树),就很优秀就是了。
大概是\(\mathcal O(n \log n)\)的。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 750005;
struct tr {
    int tot, lnk[N], nxt[N << 1], son[N << 1], w[N << 1];
    tr () {
        tot = 1;
        memset(lnk, 0, sizeof lnk);
        memset(nxt, 0, sizeof nxt);
    }
    void add (int x, int y, int z) {
        nxt[++tot] = lnk[x], lnk[x] = tot, son[tot] = y, w[tot] = z;
    }
    void adds (int x, int y, int z) {
        add(x, y, z), add(y, x, z);
    }
} T0, T1, T2;

int n, m, s, e, mn, tot; ll ans;
int sz[N], *pos[N], rt[N], las[N], lc[N * 20], rc[N * 20];
ll dep[N], mx[N * 20][2];
bool ban[N << 1];
void append (int x, int y, int z) {
    T1.adds(las[x], ++m, 0), las[x] = m;
    T1.adds(m, y, z);
}
void build (int x, int p, ll d) {
    dep[x] = d;
    for (int j = T0.lnk[x]; j; j = T0.nxt[j]) {
        if (T0.son[j] != p) {
            append(x, T0.son[j], T0.w[j]);
            build(T0.son[j], x, d + T0.w[j]);
        }
    }
}
void gete (int x, int p) {
    sz[x] = 1;
    for (int j = T1.lnk[x], v; j; j = T1.nxt[j]) {
        if (!ban[j >> 1] && T1.son[j] != p) {
            gete(T1.son[j], x);
            sz[x] += sz[T1.son[j]];
            v = max(sz[T1.son[j]], s - sz[T1.son[j]]);
            if (mn > v) {
                e = j, mn = v;
            }
        }
    }
}
void dfs (int x, int p, ll d, bool f) {
    if (x <= n) {
        *pos[x] = ++tot;
        mx[tot][f] = d + dep[x];
        mx[tot][f ^ 1] = -1e18;
        pos[x] = (f ? &rc[tot] : &lc[tot]);
    }
    for (int j = T1.lnk[x]; j; j = T1.nxt[j]) {
        if (!ban[j >> 1] && T1.son[j] != p) {
           dfs(T1.son[j], x, d + T1.w[j], f);
        }
    }
}
void dac (int x, int curs) {
    if (curs > 1) {
        s = curs, e = -1, mn = 1e9;
        gete(x, 0);
        ban[e >> 1] = 1;
        int u = T1.son[e ^ 1], v = T1.son[e], su = s - sz[v], sv = sz[v];
        dfs(u, 0, 0, 0), dfs(v, 0, T1.w[e], 1);
        dac(u, su), dac(v, sv);
    }
}
int merge (int x, int y, ll d) {
    if (!x || !y) {
        return x | y;
    }
    ans = max(ans, (mx[x][0] + mx[y][1]) / 2 - d);
    ans = max(ans, (mx[y][0] + mx[x][1]) / 2 - d);
    mx[x][0] = max(mx[x][0], mx[y][0]);
    mx[x][1] = max(mx[x][1], mx[y][1]);
    lc[x] = merge(lc[x], lc[y], d);
    rc[x] = merge(rc[x], rc[y], d);
    return x;
}
void solve (int x, int p, ll d) {
    ans = max(ans, dep[x] - d);
    for (int j = T2.lnk[x]; j; j = T2.nxt[j]) {
        if (T2.son[j] != p) {
            solve(T2.son[j], x, d + T2.w[j]);
            rt[x] = merge(rt[x], rt[T2.son[j]], d);
        }
    }
}
int main () {
    scanf("%d", &n);
    for (int i = 1, x, y, z; i < n; ++i) {
        scanf("%d%d%d", &x, &y, &z);
        T0.adds(x, y, z);
    }
    for (int i = 1; i <= n; ++i) {
        las[i] = i, pos[i] = &rt[i];
    }
    m = n;
    build(1, 0, 0);
    dac(1, m);
    for (int i = 1, x, y, z; i < n; ++i) {
        scanf("%d%d%d", &x, &y, &z);
        T2.adds(x, y, z);
    }
    solve(1, 0, 0);
    printf("%lld\n", ans);
    return 0;
}

转载于:https://www.cnblogs.com/psimonw/p/11480989.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值