最大权闭合子图
若有向图 \(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;
}