1. 程式人生 > >BZOJ2229: [Zjoi2011]最小割(最小割樹)

BZOJ2229: [Zjoi2011]最小割(最小割樹)

傳送門

最小割樹

演算法

初始時把所有點放在一個集合
從中任選兩個點出來跑原圖中的最小割
然後按照 s s 集合與 t t 集合的歸屬把當前集合劃分成兩個集合,遞迴處理
這樣一共跑了 n

1 n − 1 次最小割
可以證明圖中任意一對點之間的最小割的數值都包含在這 n 1 n − 1
個數值當中
把每次求出的最小割看成是兩個點之間的邊,可以建出一棵樹

定理1

任意三點之間的最小割一定是兩個相等的較小值和一個較大值

證明
設任意三點 a , b , c a, b, c

之間的最小割分別為 m i n c u t ( a , b ) , m i n c u t ( a , c ) , m i n c u t ( b , c ) mincut(a, b), mincut(a, c), mincut(b, c)
m i n c u t ( a , b ) mincut(a, b) 是這三者中的最小值
那麼在做 a b a − b 割時, c c 一定屬於其中的某一個集合,設其與 a a 同屬於一個集合
那麼就有 m i n c u t ( b , c ) m i n c u t ( a , b ) mincut(b, c) \le mincut(a, b)
又因為 m i n c u t ( a , b ) m i n c u t ( b , c ) mincut(a, b) \le mincut(b, c) ,所以兩者相等

定理2

圖中至多隻有 n 1 n − 1 種不同的最小割

證明
編了一個構造的證明
首先根據定理 1 1 ,可以轉化成如下問題:

n n 個點的無向完全圖,要給每一條邊染色,要求每個三元環上有兩條邊同色,求最多可以染多少種顏色

考慮歸納法
假設現在已經有一條長度大於 1 1 的鏈 a a b b 上的邊的顏色互不相同
考慮染 ( a , b ) (a,b) 這條邊
如果鏈長 = 2 =2 ,那麼 ( a , b ) (a,b) 只能選擇鏈上某條邊的顏色
如果鏈長 > 2 >2 ,假設兩個端點在鏈上的不是 ( a , b ) (a,b) 的邊的顏色都是鏈上某條邊的顏色
那麼取出其中兩條 ( a , c ) (a,c) ( c , b ) (c,b) ( a , b ) (a,b) 只能選擇兩個中其中一條邊的顏色,即鏈上某條邊的顏色

所以可以得到,顏色互不相同的邊不能形成環,所以最優的顯然是樹,即 n 1 n-1 中顏色

Sol

至此問題已經完美解決
建出最小割樹,任意兩個點的最小割就是路徑邊權最小值
直接暴力也可以
複雜度貌似 Θ ( n 3 m ) \Theta(n^3m) ? ? ? ???

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

const int maxn(505);
const int inf(1e9);

int first[maxn], n, m, cnt, lev[maxn], vis[maxn], s, t, id[maxn], tmp[maxn], cur[maxn], mincut[maxn][maxn], record[maxn << 4];
queue <int> q;

struct Edge {
    int to, next, w;
} edge[maxn << 4];

inline void Add(int u, int v, int w) {
    edge[cnt] = (Edge){v, first[u], w}, first[u] = cnt++;
    edge[cnt] = (Edge){u, first[v], w}, first[v] = cnt++;
    record[cnt - 1] = record[cnt - 2] = w;
}

inline int Bfs() {
    memset(lev, 0, sizeof(lev)), lev[s] = 1, q.push(s);
    register int u, e, v;
    while (!q.empty()) {
        for (u = q.front(), q.pop(), e = first[u]; ~e; e = edge[e].next)
            if (edge[e].w && !lev[v = edge[e].to]) lev[v] = lev[u] + 1, q.push(v);
    }
    return lev[t];
}

int Dfs(int u, int maxf) {
    if (u == t) return maxf;
    register int ret = 0, &e = cur[u], f, v;
    for (; ~e; e = edge[e].next)
        if (edge[e].w && lev[v = edge[e].to] == lev[u] + 1){
            f = Dfs(v, min(edge[e].w, maxf - ret));
            ret += f, edge[e].w -= f, edge[e ^ 1].w += f;
            if (ret == maxf) return ret;
        }
    if (!ret) lev[u] = 0;
    return ret;
}

inline int Dinic() {
    register int ret = 0, i;
    for (i = 0; i < cnt; ++i) edge[i].w = record[i];
    while (Bfs()) memcpy(cur, first, sizeof(cur)), ret += Dfs(s, inf);
    return ret;
}

void Mark(int u) {
    if (vis[u]) return;
    register int e;
    for (vis[u] = 1, e = first[u]; ~e; e = edge[e].next) if (edge[e].w) Mark(edge[e].to);
}

inline void Solve(int l, int r) {
    if (l >= r) return;
    memset(vis, 0, sizeof(vis)), s = id[l], t = id[r];
    register int i, j, mid, d = Dinic();
    for (Mark(s), j = 0, i = l; i <= r; ++i) if (vis[id[i]]) tmp[++j] = id[i];
    for (mid = l + j - 1, i = l; i <= r; ++i) if (!vis[id[i]]) tmp[++j] = id[i];
    for (i = l; i <= r; ++i) id[i] = tmp[i - l + 1];
    for (i = 1; i <= n; ++i)
        if (vis[i])
            for (j = 1; j <= n; ++j)
                if (i != j && !vis[j]) mincut[i][j] = mincut[j][i] = min(mincut[i][j], d);
    Solve(l, mid), Solve(mid + 1, r);
}

int main() {
    register int i, j, u, v, w, test;
    scanf("%d", &test);
    while (test--) {
        memset(first, -1, sizeof(first)), cnt = 0;
        scanf("%d%d", &n, &m);
        for (i = 1; i <= m; ++i) scanf("%d%d%d", &u, &v, &w), Add(u, v, w);
        for (i = 1; i <= n; ++i) id[i] = i;
        memset(mincut, 63, sizeof(mincut)), Solve(1, n), scanf("%d", &w);
        while (w--) {
            scanf("%d", &u), v = 0;
            for (i = 1; i <= n; ++i)
                for (j = i + 1; j <= n; ++j) v += mincut[i][j] <= u;
            printf("%d\n", v);
        }
        putchar('\n');
    }
    return 0;
}

參考文獻

  1. 某鴿姓選手的網路流課件
    2. 垃圾博主自己 yy