算法學習:邊分治+bzoj2870: 最長道路tree

算法學習:邊分治

前言

本來作爲一名標準的NOIP退役選手,老早想要再見OI+停更+讀文化課一波行雲流水的操作了,結果D類居然還有,於是繼續苟!然後就有了日常賽前學算法。

例題

bzoj2870: 最長道路tree
權限題來着,題目大意如下:
給定一棵N個點的樹,求樹上一條鏈使得鏈的長度乘鏈上所有點中的最小權值所得的積最大。
其中鏈長度定義爲鏈上點的個數。

分析

關於樹上路徑問題,想必大家都清楚著名的點分治算法(否則就不會學什麼邊分治了),然而這道題如果採用點分治的話也不是不行,就是會瘋掉。子樹合併的時候很難搞。
這時候有一種就要採用邊分治。邊分治作爲點分治的替代品,有非常優秀的性質,等會會提到。下面先說明邊分治的過程。

邊分治

點分治是每一次採用樹的重心進行分治,換而言之就是最大子樹最小的,那麼邊分治類比一下,每次尋找邊連接的兩個子樹最大值最小的那天邊,更形式化地表述,尋找min{(u,v)Emax(sizeu,sizev)}min\{(u,v)\in E|max(size_u,size_v)\}

void Rt(int u, int fa) {
    sz[u] = 1; cnt += (u <= n);
    for(int i = T.pr[u], v; i; i = T.nx[i])
        if(!del[i >> 1] && (v = T.to[i]) != fa) {
            Rt(v, u), sz[u] += sz[v];
            int tmp = std::max(sz[v], sums - sz[v]);
            if(mn > tmp) mn = tmp, rt = i;
        }
} 

這個算法的複雜度有保證嗎?鏈卡一卡?菊花圖卡一卡?菊花圖卡掉了!
於是我們需要改進一下這個算法。

知識點:多叉樹轉二叉樹

我們發現鏈卡不掉,二叉樹也卡不掉。
於是我們考慮用左兒子右兄弟表示法轉多叉樹。
也就是把原本uvii[1,n]u\to v_i|i\in [1,n]
變成uv1,vivi+1i[1,n)u\to v_1,v_i \to v_{i+1}|i\in [1,n)
但是這個方法有一個弊端,就是我們就連兩點間距離這個樹的最基本特徵都改變了。
有一種神奇的方法可以保留這個性質。
新建節點x1,x2,xnx_1,x_2,\cdots x_n
然後這樣連
ux1,xixi+1i[1,n),xiwii[1,n]u\to x_1,x_i \to x_{i+1}|i\in [1,n),x_i\to w_i|i\in [1,n]
對於(u,x)(u,x),(x,x)(x,x)之間的邊權爲0,然後把(u,wi)(u,w_i)的邊權轉移到(xi,wi)(x_i,w_i)上。
這樣的話就OK啦。

void Ins(int u, int v) {
    ++tot; T.adds(tot, v, 1); ::v[tot] = ::v[lst[u]];
    T.adds(lst[u], tot, 0); lst[u] = tot;
}
void Build(int u, int fa) {
    for(int i = G.pr[u], v; i;i = G.nx[i])
        if((v = G.to[i]) != fa)
            Ins(u, v), Build(v, u);
}

例題

邊分治有啥好處嘞?
你會發現,它分治的時候只有兩棵子樹。
這樣多子樹合併變成了雙子樹合併。
對於這道題而言,考慮把經過這條邊的路徑合併。
我們把兩邊子樹的從根出發的路徑都提出來,這樣的話問題轉化成:
每條鏈有長度和權值兩個屬性,把兩個鏈合併得到的是權值的最小值乘上長度和。
按照權值排序。雙指針掃一遍即可。

代碼

很好寫滴。

#include<bits/stdc++.h>
#define fi first
#define se second
#define mp std::make_pair
const int N = 1e5 + 10, M = 2e5 + 10, inf = 1e9;
typedef std::pair<int, int> pa;
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);}
}G, T;
int tot, lst[N], sz[N], v[N], top[2], cnt, sums, rt, n, mn;
bool del[N]; pa st[2][N]; long long ans;
void Ins(int u, int v) {
    ++tot; T.adds(tot, v, 1); ::v[tot] = ::v[lst[u]];
    T.adds(lst[u], tot, 0); lst[u] = tot;
}
void Build(int u, int fa) {
    for(int i = G.pr[u], v; i;i = G.nx[i])
        if((v = G.to[i]) != fa)
            Ins(u, v), Build(v, u);
}
void Rt(int u, int fa) {
    sz[u] = 1; cnt += (u <= n);
    for(int i = T.pr[u], v; i; i = T.nx[i])
        if(!del[i >> 1] && (v = T.to[i]) != fa) {
            Rt(v, u), sz[u] += sz[v];
            int tmp = std::max(sz[v], sums - sz[v]);
            if(mn > tmp) mn = tmp, rt = i;
        }
} 
void Dfs(int u, int fa, int len, int mn, int c) {
    mn = std::min(v[u], mn); st[c][++top[c]] = mp(mn, len);
    for(int i = T.pr[u], v; i;i = T.nx[i])
        if(!del[i >> 1] && (v = T.to[i]) != fa)
            Dfs(v, u, len + T.w[i], mn, c);
}
void Get(int c, int w) {
    pa *f = st[c], *g = st[c ^ 1];
    for(int i = top[c], j = top[c ^ 1], mx = 0; i; --i) {
        for(;j && g[j].fi >= f[i].fi;)
            mx = std::max(mx, g[j--].se);
        if(j < top[c ^ 1])
            ans = std::max(ans, 1LL * 1LL * (mx + f[i].se + w + 1) * f[i].fi);
    }
}
void Div(int u, int pres) {
    if(pres == 1) return ;
    mn = inf; sums = pres; Rt(u, 0);
    del[rt >> 1] = true;
    top[0] = top[1] = 0; 
    Dfs(T.to[rt], 0, 0, inf, 0);
    Dfs(T.to[rt ^ 1], 0, 0, inf, 1);
    std::sort(st[0] + 1, st[0] + top[0] + 1);
    std::sort(st[1] + 1, st[1] + top[1] + 1);
    Get(0, T.w[rt]); Get(1, T.w[rt]);
    int nw = rt, S2 = pres - sz[T.to[rt]];
    Div(T.to[nw], sz[T.to[rt]]); Div(T.to[nw ^ 1], S2);
}
int main() {
    n = ri();
    for(int i = 1;i <= n; ++i)
        v[i] = ri();
    for(int i = 1;i < n; ++i)
        G.adds(ri(), ri());
    for(int i = 1;i <= n; ++i)
        lst[i] = i;
    tot = n; Build(1, 0);
    Div(1, tot);
    printf("%lld\n", ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章