@atcoder - CODE FESTIVAL 2017 Final - J@ Tree MST


@description@

給定 N 個點,第 i 點有一個點權 Xi,再給定一棵邊帶權的樹,第 i 條 (Ai, Bi) 邊權爲 Ci。算法

構建一個徹底圖,徹底圖中邊 (i, j) 的邊權爲 dist(i, j) + Xi + Xj,其中 dist(i, j) 是點 i 與點 j 在樹上的距離。數據結構

求該徹底圖的最小生成樹。優化

Constraints
2≤N≤200000; 1≤Xi≤10^9; 1≤Ai,Bi≤N; 1≤Ci≤10^9spa

Input
輸入形式以下:
N
X1 X2 … XN
A1 B1 C1
A2 B2 C2
:
AN−1 BN−1 CN−1code

Output
輸出最小生成樹的邊權總和。隊列

Sample Input 1
4
1 3 5 1
1 2 1
2 3 2
3 4 3
Sample Output 1
22ip

選擇邊 (1, 2), (1, 4), (3, 4),邊權總和爲 5 + 8 + 9 = 22get

點此跳轉到題目it

@solution@

徹底圖並不能直接套 prim 或者 kruskal。
而且也不像最小曼哈頓生成樹同樣,有比較好用的性質能夠去除大量無用邊。

考慮這類題的一個通用解法:boruvka(不會念)。
實際上是基本無人問津的最小生成樹算法(我也不知道爲何),可是能夠將其算法思想拓展,解決一些徹底圖的最小生成樹問題。
說是徹底圖,邊權也有性質,並且每每會和點權掛鉤(好比這道題)。

扯了這麼多,所謂的 boruvka 算法是什麼呢?
咱們對於每個連通塊去找與它相鄰的最小邊。能夠證實這樣的邊必定是存在於最小生成樹之中(破圈法之類的都能證)。
因而咱們把這些邊加入最小生成樹,並將連通塊合併。
由於每次選最小邊的時候,一條邊最多會被選兩次,因此最多執行 log 次尋找最小邊的操做。

而這類題的特色是:每每尋找最小邊的過程能夠優化。

什麼?你說這道題須要點分治來尋找路徑最小值?而後複雜度就炸成 O(nlog^3n) 了?
NONONO。咱們其實能夠 O(n) 一次性給全部點找到它對應的最小值。

考慮全部連通塊都是一個點的時候,咱們能夠作一個換根 dp 求出全部點的最小邊(求距離一個點最近的點)。
因爲是找最小值,重複通過一條邊確定不優,咱們甚至不用去存 dp 的次小值,來避免從上往下傳遞 dp 值的時候走入同一棵子樹。
這個過程是 O(n) 的。

那麼連通塊是一堆點的時候,咱們怎麼去排除與它同一連通塊的點呢?
其實。。。很簡單嘛。
假如最小邊是同一連通塊的,咱們就去找與最小邊不在同一連通塊的次小邊,不就解決了嘛。
dp 的時候存兩維:最小邊,與不和最小邊在同一連通塊的次小邊。轉移時討論一下便可。
一次求最小邊的複雜度爲 O(n),因此總複雜度爲 O(nlogn)。

@accepted code@

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
#define mp make_pair
#define fi first
#define se second
typedef pair<ll, int> pli;
typedef pair<pli, pli> st;
const int MAXN = 200000;
const ll INF = (1LL<<60);
struct edge{
    int to; ll dis;
    edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt = edges;
void addedge(int u, int v, int w) {
    edge *p = (++ecnt);
    p->to = v, p->dis = w, p->nxt = adj[u], adj[u] = p;
    p = (++ecnt);
    p->to = u, p->dis = w, p->nxt = adj[v], adj[v] = p;
}
pli lnk[MAXN + 5];
int fa[MAXN + 5], clr[MAXN + 5];
ll X[MAXN + 5];
int find(int x) {
    return fa[x] = (fa[x] == x ? x : find(fa[x]));
}
bool unite(int x, int y) {
    int fx = find(x), fy = find(y);
    if( fx != fy ) {
        fa[fx] = fy;
        return true;
    }
    else return false;
}
st dp[MAXN + 5];
void update(st &a, st b) {
    if( b.fi.fi < a.fi.fi ) {
        if( b.fi.se != a.fi.se )
            a.se = a.fi;
        a.fi = b.fi;
        if( b.se.fi < a.se.fi )
            a.se = b.se;
    }
    else {
        if( b.fi.se != a.fi.se ) {
            if( b.fi.fi < a.se.fi )
                a.se = b.fi;
        }
        else if( b.se.fi < a.se.fi )
            a.se = b.se;
    }
}
void dfs1(int x, int f) {
    dp[x] = mp(mp(X[x], clr[x]), mp(INF, -1));
    for(edge *p=adj[x];p;p=p->nxt) {
        if( p->to == f ) continue;
        dfs1(p->to, x); st t = dp[p->to];
        t.fi.fi += p->dis, t.se.fi += p->dis;
        update(dp[x], t);
    }
}
void dfs2(int x, int f, st k) {
    update(dp[x], k);
    for(edge *p=adj[x];p;p=p->nxt) {
        if( p->to == f ) continue;
        st t = dp[x];
        t.fi.fi += p->dis, t.se.fi += p->dis;
        dfs2(p->to, x, t);
    }
}
int num[MAXN + 5];
int main() {
    int N; scanf("%d", &N);
    for(int i=1;i<=N;i++)
        scanf("%lld", &X[i]), fa[i] = i;
    for(int i=1;i<N;i++) {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        addedge(u, v, w);
    }
    ll ans = 0;
    while( true ) {
        int cnt = 0;
        for(int i=1;i<=N;i++)
            if( fa[i] == i ) num[clr[i] = (++cnt)] = i;
        if( cnt == 1 ) break;
        for(int i=1;i<=N;i++)
            clr[i] = clr[find(i)];
        dfs1(1, 0), dfs2(1, 0, mp(mp(INF, -1), mp(INF, -1)));
        for(int i=1;i<=cnt;i++)
            lnk[i] = mp(INF, -1);
        for(int i=1;i<=N;i++) {
            if( dp[i].fi.se == clr[i] )
                lnk[clr[i]] = min(lnk[clr[i]], mp(dp[i].se.fi + X[i], dp[i].se.se));
            else lnk[clr[i]] = min(lnk[clr[i]], mp(dp[i].fi.fi + X[i], dp[i].fi.se));
        }
        for(int i=1;i<=cnt;i++)
            if( unite(num[i], num[lnk[i].se]) )
                ans += lnk[i].fi;
    }
    printf("%lld\n", ans);
}

@details@

被數據結構困住的我,用點分治 + 優先隊列寫了一個 prim。
而後它 MLE 了。
當時我就哭了。

咳咳。不過也算是增加了一點見識,同時還告訴我一個深入的道理:不要被套路所困。不必定非得要數據結構才能維護的啊。

相關文章
相關標籤/搜索