【網路流24題】 9. 方格取數問題 題解
阿新 • • 發佈:2020-09-07
題意
有一個\(m\)行\(n\)列的方格圖,每個方格中都有一個正整數。現要從方格中取數,使任意兩個數所在方格沒有公共邊,且取出的數的總和最大,請求出最大的和。
思路
顯然,這是一個選A就不能選B的那種題。網路流本身就很適合解決這種問題。網路流表示互斥的一種常用方法——在互斥的兩個點之間連一條邊。這樣,我們有了基本的思路。
然後,我們還要考慮連源點和匯點。我們顯然不能讓所有的點都連上源點和匯點,那樣的話網路流就沒有意義了。所以,我們可以考慮拆點,把點\(i\)拆成\((x_i, y_i)\)。然後,我們把源點向所有\(x_i\)連一條容量為該點大小的邊,從所有\(y_i\)
對於上面說到的連邊,我們把互斥的點連邊改成——若\(i\)和\(j\)互斥,則在\(x_i\)和\(y_j\)之間連一條容量為\(INF\)的邊。
然後,我們考慮它的含義:如果某一條(組)邊的流量跑滿了,它的含義是什麼?
(一組邊指的是,如果\(x_1 \rightarrow y_2, x_1 \rightarrow y_3, x_2 \rightarrow y_3\),那麼稱\(1,2,3\)為一組點,它們之間相互連線的邊成為一組邊)
顯然是,如果這一組的一些邊跑滿了,另一些邊沒跑滿,那麼顯然,根據邊容量的含義,我們選擇沒跑滿的那些要更優。所以,我們選擇捨棄掉跑滿的邊要更優。這顯然就是最小割。所以,我們用所有點的和減去最小割就是答案了。
程式碼
/** * luogu P2774 https://www.luogu.com.cn/problem/P2774 **/ #include <cstdio> #include <algorithm> #include <cstring> #include <queue> using namespace std; const int maxn = 5e4 + 5; const int maxm = 2e5 + 5; const int S = 0; const int T = maxn - 1; const int INF = 0x3f5f5f5f; struct Edge { int to, nxt, val; }e[maxm]; int numedge, head[maxn], n, m, x, sum, depth[maxn]; inline void _ADD(int from, int to, int val) { e[numedge].to = to; e[numedge].val = val; e[numedge].nxt = head[from]; head[from] = numedge; numedge++; } inline void AddEdge(int from, int to, int val) { _ADD(from, to, val); _ADD(to, from, 0); } inline bool bfs() { memset(depth, 0, sizeof(depth)); depth[S] = 1; queue<int> q; q.push(S); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = e[i].nxt) { int to = e[i].to; if (!depth[to] && e[i].val > 0) { depth[to] = depth[u] + 1; q.push(to); } } } return depth[T]; } int dfs(int u, int flow) { if (u == T || !flow) return flow; int res = 0; for (int i = head[u]; ~i; i = e[i].nxt) { int to = e[i].to; if (depth[to] > depth[u] && e[i].val) { int di = dfs(to, min(flow, e[i].val)); if (di) { flow -= di; e[i].val -= di; e[i ^ 1].val += di; res += di; } } } if (!res) depth[u] = 0; return res; } inline int Dinic() { int res = 0; while (bfs()) res += dfs(S, INF); return res; } int main() { memset(head, -1, sizeof(head)); scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { scanf("%d", &x); sum += x; if (!((i + j) & 1)) { AddEdge(S, (i - 1) * m + j, x); for (int k = -1; k <= 1; k += 2) { if (i + k > 0 && i + k <= n) AddEdge((i - 1) * m + j, (i + k - 1) * m + j, INF); if (j + k > 0 && j + k <= m) AddEdge((i - 1) * m + j, (i - 1) * m + j + k, INF); } } else { AddEdge((i - 1) * m + j, T, x); } } } printf("%d\n", sum - Dinic()); return 0; }