[CTSC2018]暴力寫掛 邊分治+啓發式合併

[CTSC2018]暴力寫掛 邊分治+虛樹

傳送門:
bzoj
luogu

分析

題目大意:給倆樹T,TT,T’,求depx+depy(depLca(x,y)+depLca(x,y))dep_x+dep_y-(dep_{Lca(x,y)}+dep'_{Lca'(x,y)})的最大值。

比較不好處理的是depLca(x,y)dep_{Lca(x,y)}
Dis(x,y)=depx+depy2depLca(x,y)Dis(x,y)=dep_x+dep_y-2dep_{Lca(x,y)}可以得到
depLca(x,y)=12(depx+depyDis(x,y))dep_{Lca(x,y)}=\frac{1}{2}(dep_x+dep_y-Dis(x,y))
帶進去化簡一下可以得到:
2Ans=max(depx+depy+Dis(x,y)depLca(x,y))2Ans=max(dep_x+dep_y+Dis(x,y)-dep'_{Lca'(x,y)})
事實上depx+depy+Dis(x,y)dep_x+dep_y+Dis(x,y)這個東西可以直接用邊分治來處理,假設當前邊是(st,ed)(st,ed),那麼設wx=(Dis(x,st)+depx)w_x=(Dis(x,st)+dep_x),在一次分治中變成要最大化wx+wydepLca(x,y)w_x+w_y-dep'_{Lca'(x,y)},直接上虛樹即可。
不過複雜度是O(nlog2n)O(nlog^2n)的,據說出題人卡掉了。
有一種更具技巧性的做法,充分利用了邊分治的性質。
我們發現,邊分治有一個特點,就是每一層分治中,一個點要麼再一條邊的“左邊”,要麼在一條邊的“右邊”,這也是邊分治優於點分治的地方——每一層只有兩顆子樹。
這個時候有一種操作是,根據分治樹的結構每個節點動態地開一顆二叉樹(暫且稱其爲xxcc樹)。如果在分治邊的左邊就把它放左邊,否則把它放右邊。
不難發現這棵樹的結構和功能與動態開點的線段樹是類似的,當然,也支持啓發式合併操作。
那麼我們仍舊處理出wxw_x,只不過這回將其維護在xxcc樹的每個節點上,然後遍歷另一棵樹,枚舉LcaLca',那麼只需要處理出子樹內部的{wx+wy}\{w_x+w_y\}最大值即可,這個東西可以在啓發式合併的時候統計出來。
注意處理只有一個點的情況。
複雜度同線段樹合併的複雜度是一樣的,均攤的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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章