1. 程式人生 > >P2805 [NOI2009]植物大戰僵屍

P2805 [NOI2009]植物大戰僵屍

%d 網絡 tor pac std void else 最小割 所有

題目地址:P2805 [NOI2009]植物大戰僵屍

最大權閉合子圖

若有向圖 \(G\) 的子圖 \(V\) 滿足: \(V\) 中頂點的所有出邊均指向 \(V\) 內部的頂點,則稱 \(V\)\(G\) 的一個閉合子圖

\(G\) 中的點有點權,則點權和最大的閉合子圖稱為有向圖 \(G\)最大權閉合子圖

構圖方法

建立源點 \(S\) 和匯點 \(T\) ,源點 \(S\) 連所有點權為正的點,容量為該點點權;其余點連匯點 \(T\) ,容量為該點點權的相反數,對於原圖中的邊 \((x,y)\) ,連邊 \((x,y,+inf)\)

定理

  • 最大權閉合圖的點權和 \(=\) 所有正權點權值和 \(–\)
    最小割。
  • 上述圖的最小割包含 \(S\)不在最大權閉合圖內的正權節點的邊和在最大權閉合圖內的負權節點\(T\) 的邊。

推論(最大權閉合圖方案)

殘量網絡中由源點 \(S\) 能夠訪問到的點,就構成一個點數最少的最大權閉合圖。

本題題解

把每個植物當做一個頂點,植物攜帶的能源數目為頂點的權值。

如果植物 \(b\) 在植物 \(a\) 的攻擊範圍內,連接一條有向邊 \((a,b)\) ,表示 \(a\) 可以保護 \(b\)

由於僵屍從右向左進攻,可以認為每個植物都被它右邊相鄰的植物保護,對於每個植物 \(a\) (除最左邊一列),向其左邊的相鄰植物 \(b\) ,連接一條有向邊 \((a,b)\)

此時可能有一些植物是互相保護的,都不能被吃掉,這樣的點(和與其相連的邊)應該全部刪掉,拓撲排序一遍即可。

如果要吃掉一個植物,就應該把所有保護它的植物全部吃掉。

對應在圖中,如果我們將圖轉置(即所有邊轉成其反向邊),那麽可以吃掉的植物應該構成一個閉合子圖,而最優解就是最大權閉合子圖。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 6, M = 1e6 + 6, inf = 1e9;
int n, m, s, t, a[N], ans, d[N], deg[N], v[N];
int Head[N], Edge[M], Leng[M], Next[M], tot = 1;
queue<int> q;
vector<int> e[N];

inline void add(int x, int y, int z) {
    Edge[++tot] = y;
    Leng[tot] = z;
    Next[tot] = Head[x];
    Head[x] = tot;
}

inline bool bfs() {
    memset(d, 0, sizeof(d));
    queue<int> q;
    q.push(s);
    d[s] = 1;
    while (q.size()) {
        int x = q.front();
        q.pop();
        for (int i = Head[x]; i; i = Next[i]) {
            int y = Edge[i], z = Leng[i];
            if (deg[y] || d[y] || !z) continue;
            q.push(y);
            d[y] = d[x] + 1;
            if (y == t) return 1;
        }
    }
    return 0;
}

int dinic(int x, int flow) {
    if (x == t) return flow;
    int rest = flow;
    for (int i = Head[x]; i && rest; i = Next[i]) {
        int y = Edge[i], z = Leng[i];
        if (d[y] != d[x] + 1 || !z) continue;
        int k = dinic(y, min(rest, z));
        if (!k) d[y] = 0;
        else {
            Leng[i] -= k;
            Leng[i^1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}

int main() {
    cin >> n >> m;
    s = n * m, t = s + 1;
    for (int i = 0; i < s; i++) {
        scanf("%d", &a[i]);
        int k;
        scanf("%d", &k);
        while (k--) {
            int x, y;
            scanf("%d %d", &x, &y);
            e[i].push_back(x * m + y);
            ++deg[x*m+y];
        }
    }
    for (int i = 0; i < n; i++)
        for (int j = 1; j < m; j++) {
            e[i*m+j].push_back(i * m + j - 1);
            ++deg[i*m+j-1];
        }
    for (int i = 0; i < s; i++)
        if (!deg[i]) q.push(i), v[i] = 1;
    while (q.size()) {
        int x = q.front();
        q.pop();
        for (unsigned int i = 0; i < e[x].size(); i++) {
            int y = e[x][i];
            if (!v[y] && !--deg[y]) q.push(y), v[y] = 1;
        }
    }
    for (int x = 0; x < s; x++) {
        if (!v[x]) continue;
        for (unsigned int i = 0; i < e[x].size(); i++) {
            int y = e[x][i];
            if (!v[y]) continue;
            add(y, x, inf);
            add(x, y, 0);
        }
        if (a[x] > 0) add(s, x, a[x]), add(x, s, 0), ans += a[x];
        if (a[x] < 0) add(x, t, -a[x]), add(t, x, 0);
    }
    int now = 0;
    while (bfs())
        while ((now = dinic(s, inf)))
            ans -= now;
    cout << ans << endl;
    return 0;
}

P2805 [NOI2009]植物大戰僵屍