[CTSC2018]暴力写挂 边分治+启发式合并

[CTSC2018]暴力写挂 边分治+虚树

传送门:
bzoj
luogu

分析

题目大意:给俩树 T , T ’ T,T’ T,T,求 d e p x + d e p y − ( d e p L c a ( x , y ) + d e p L c a ′ ( x , y ) ′ ) dep_x+dep_y-(dep_{Lca(x,y)}+dep'_{Lca'(x,y)}) depx+depy(depLca(x,y)+depLca(x,y))的最大值。

比较不好处理的是 d e p L c a ( x , y ) dep_{Lca(x,y)} depLca(x,y)
D i s ( x , y ) = d e p x + d e p y − 2 d e p L c a ( x , y ) Dis(x,y)=dep_x+dep_y-2dep_{Lca(x,y)} Dis(x,y)=depx+depy2depLca(x,y)可以得到
d e p L c a ( x , y ) = 1 2 ( d e p x + d e p y − D i s ( x , y ) ) dep_{Lca(x,y)}=\frac{1}{2}(dep_x+dep_y-Dis(x,y)) depLca(x,y)=21(depx+depyDis(x,y))
带进去化简一下可以得到:
2 A n s = m a x ( d e p x + d e p y + D i s ( x , y ) − d e p L c a ′ ( x , y ) ′ ) 2Ans=max(dep_x+dep_y+Dis(x,y)-dep'_{Lca'(x,y)}) 2Ans=max(depx+depy+Dis(x,y)depLca(x,y))
事实上 d e p x + d e p y + D i s ( x , y ) dep_x+dep_y+Dis(x,y) depx+depy+Dis(x,y)这个东西可以直接用边分治来处理,假设当前边是 ( s t , e d ) (st,ed) (st,ed),那么设 w x = ( D i s ( x , s t ) + d e p x ) w_x=(Dis(x,st)+dep_x) wx=(Dis(x,st)+depx),在一次分治中变成要最大化 w x + w y − d e p L c a ′ ( x , y ) ′ w_x+w_y-dep'_{Lca'(x,y)} wx+wydepLca(x,y),直接上虚树即可。
不过复杂度是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的,据说出题人卡掉了。
有一种更具技巧性的做法,充分利用了边分治的性质。
我们发现,边分治有一个特点,就是每一层分治中,一个点要么再一条边的“左边”,要么在一条边的“右边”,这也是边分治优于点分治的地方——每一层只有两颗子树。
这个时候有一种操作是,根据分治树的结构每个节点动态地开一颗二叉树(暂且称其为xxcc树)。如果在分治边的左边就把它放左边,否则把它放右边。
不难发现这棵树的结构和功能与动态开点的线段树是类似的,当然,也支持启发式合并操作。
那么我们仍旧处理出 w x w_x wx,只不过这回将其维护在xxcc树的每个节点上,然后遍历另一棵树,枚举 L c a ′ Lca' Lca,那么只需要处理出子树内部的 { w x + w y } \{w_x+w_y\} {wx+wy}最大值即可,这个东西可以在启发式合并的时候统计出来。
注意处理只有一个点的情况。
复杂度同线段树合并的复杂度是一样的,均摊的 O ( n l o g n ) O(nlogn) O(nlogn)

代码

#include<bits/stdc++.h>
const int N = 8e5 + 10, M = N << 1, B = N * 20, inf = 1e9;
const long long oo = 1e18;
int ri() {
    char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f  = -1;
    for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
struct Edge {
    int to[M], nx[M], w[M], pr[N], tp;
    Edge() {tp = 1;}
    void add(int u, int v, int _w) {to[++tp] = v; nx[tp] = pr[u]; pr[u] = tp; w[tp] = _w;}
    void adds(int u, int v, int w = 0) {add(u, v, w); add(v, u, w);}
}R, T, S;
int lst[N], rt[N], siz[N], *pos[N], ch[B][2], n, sums, mn, tot, sz, G; 
long long dep[N], mx[B][2], ans, dec; bool del[N];
void Up(long long &a, long long b) {a = std::max(a, b);}
void ins(int u, int v, int w) {
    ++tot; T.adds(tot, v, w);
    T.adds(lst[u], tot, 0); lst[u] = tot;
}
void Build(int u, int fa, long long de) {
    dep[u] = de;
    for(int i = R.pr[u], v; i; i = R.nx[i])
        if((v = R.to[i]) != fa)
            ins(u, v, R.w[i]), Build(v, u, de + R.w[i]);
}
void Dfs(int u, int fa, long long dis, bool p) {
    if(u <= n) {
        *pos[u] = ++sz;
        mx[sz][p] = dis + dep[u];
        mx[sz][p ^ 1] = -oo;
        pos[u] = &ch[sz][p];
    }
    for(int i = T.pr[u], v; i;i = T.nx[i])
        if(!del[i >> 1] && (v = T.to[i]) != fa)
            Dfs(v, u, dis + T.w[i], p);
}
void Rt(int u, int fa) {
    siz[u] = 1;
    for(int i = T.pr[u], v; i; i = T.nx[i])
        if(!del[i >> 1] && (v = T.to[i]) != fa) {
            Rt(v, u), siz[u] += siz[v];
            int tmp = std::max(siz[v], sums - siz[v]);
            if(mn > tmp) mn = tmp, G = i;
        }
} 
void Div(int u, int pres) {
    if(pres == 1) return ;
    sums = pres; mn = inf; Rt(u, 0);
    del[G >> 1] = true;
    int x = T.to[G], y = T.to[G ^ 1], sy = pres - siz[x];
    Dfs(x, 0, 0, 0); Dfs(y, 0, T.w[G], 1);
    Div(x, siz[x]); Div(y, sy);
}
int Mg(int u, int v) {
    if(!u || !v) return u | v;
    Up(ans, mx[u][0] + mx[v][1] - dec);
    Up(ans, mx[u][1] + mx[v][0] - dec);
    mx[u][0] = std::max(mx[u][0], mx[v][0]);
    mx[u][1] = std::max(mx[u][1], mx[v][1]);
    ch[u][0] = Mg(ch[u][0], ch[v][0]);
    ch[u][1] = Mg(ch[u][1], ch[v][1]);
    return u;
}
void Work(int u, int fa, long long dis) {
    Up(ans, dep[u] - dis << 1);
    for(int i = S.pr[u], v; i; i = S.nx[i])
        if((v = S.to[i]) != fa) {
            Work(v, u, dis + S.w[i]);
            dec = dis << 1; rt[u] = Mg(rt[u], rt[v]);
        }
}
int main() {
    n = ri();
    for(int i = 1;i < n; ++i) {
        int u = ri(), v = ri(), w = ri();
        R.adds(u, v, w);
    }
    for(int i = 1;i <= n; ++i)
        lst[i] = i, pos[i] = &rt[i];
    tot = n; Build(1, 0, 0);
    Div(1, tot);
    for(int i = 1;i < n; ++i) {
        int u = ri(), v = ri(), w = ri();
        S.adds(u, v, w);
    }
    Work(1, 0, 0);
    printf("%lld\n", ans >> 1);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值