【题解】P2472 [SCOI2007] 蜥蜴 [网络流]
P2472 [SCOI2007] 蜥蜴 - 洛谷 (luogu.com.cn) (水紫)
一看到网格就想用 bfs,看到数据范围老实了。
本质上是资源分配,从一个状态达到另一个状态有代价并且限量,还要最大化结束状态的数量。
这不网络流吗?
(我真没听过 “平面距离” 这个叫法,不如直接说欧几里得距离或者曼哈顿距离,d 就是欧氏距离)
想好算法后这题就很简单了,考虑到没有源汇点,我们自己增加 S 为源点,T 为汇点。
首先,S 连大小为 1 的边到一开始有蜥蜴的格子(因为每个格子上只能有 1 只蜥蜴),
能直接到边界外面的格子连无限大的边到 T。
然后很重要的一点:本题是节点限制流量,不是边限制流量。
这代表着我们要将每个点拆成入点和出点,入点 -> 出点的边流量就是每个点的石像高度 h。
这代表能通过这个点走向别的格子的蜥蜴只能有 h 只,真正实现了单点边权做不到的点流限制。
而从其他点出点连到入点的边,容量为无限大,
这代表所有蜥蜴都可以走进来(但不一定走的出去)。
总结流程:
(0)给格子(入点出点)编号,方便管理。
(1)源点 S 连到有蜥蜴的格子入点,边权为 1。
(2)格子自己入点连到自己出点,边权为 h[x][y]。
(3)格子的出点连到能到达(欧氏距离小于等于 d)的格子入点,边权为 inf。
(4)能到达边界外面的格子出点连到汇点 T,边权为 inf。
代码(我去我 dinic 模板打错了调了俩小时再也不敢不背模板了):
#include<bits/stdc++.h>
using namespace std;typedef pair<int, int> PII;
const int N = 23;
const int M = 2 * N * N; // 网络流点数
const int E = M * M * 2; // 边数
const int INF = 1e9;int n, m, D;
int h[N][N]; char s[N][N];
int S, T;
map<PII, int> mp; // 编号 struct edge {int x, y, f, pre;
} e[E];
int elen, last[M], cur[M];void ins(int x, int y, int f) { // 我流链式前向星 elen ++; e[elen] = {x, y, f, last[x]}; last[x] = elen;elen ++; e[elen] = {y, x, 0, last[y]}; last[y] = elen;
}bool jd_bon(int x, int y) {return min(min(n - x + 1, m - y + 1), min(x, y)) <= D; // 到上下左右边界外最小的距离小于等于 D
}int get_dis(int a, int b, int c, int d) {return (a - c) * (a - c) + (b - d) * (b - d); // 整数计算避免精度
}void jd_find(int x, int y) {for (int i = 1; i <= n; i ++) {for (int j = 1; j <= m; j ++) {int t = get_dis(x, y, i, j);if (t <= D * D && t != 0) { // 在范围之内 int tx = mp[{x, y}], ty = mp[{i, j}];ins(tx ^ 1, ty, INF); //出点 -> (欧氏距离小于等于 d)入点}}}
}bool v[M]; int d[M];bool spfa() {memset(v, 0, sizeof(v)); v[S] = 1;queue<int> Q; Q.push(S); memset(d, 0x7f, sizeof(d));int inf = d[1]; d[S] = 0;while (!Q.empty()) {int x = Q.front(); Q.pop(); v[x] = 0;for (int k = last[x]; k; k = e[k].pre) if (e[k].f) { // 有流量 int y = e[k].y;if (d[y] > d[x] + 1) {d[y] = d[x] + 1;if (!v[y]) {Q.push(y);v[y] = 1;}}}} return (d[T] != inf);
}int dinic(int x, int f) {int sx = 0; v[x] = 1;if (x == T || (!f)) {return f;}for (int k = cur[x]; k; k = e[k].pre) if (e[k].f) { // 弧优化 int y = e[k].y; cur[x] = k;if (d[y] == d[x] + 1 && !v[y]) { // y 是 x 下一层的,并且还能得到流量 int sy = dinic(y, min(e[k].f, f - sx));sx += sy; e[k].f -= sy; e[k ^ 1].f += sy;if (sx == f) {return f;}}}if (sx) {v[x] = 0;}return sx;
}int main () {ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m >> D;S = 0; T = 1;int len = 1; // 格子编号从 2 开始 memset (last, 0, sizeof(last));elen = 1; // 双向边 elen = 1 for (int i = 1; i <= n; i ++) {cin >> (s[i] + 1);for (int j = 1; j <= m; j ++) {h[i][j] = s[i][j] - '0';len ++;mp[{i, j}] = len; // 偶数入点,奇数出点 len ++;}}int sum = 0; // 蜥蜴个数 for (int i = 1; i <= n; i ++) {cin >> (s[i] + 1);for (int j = 1; j <= m; j ++) if (s[i][j] == 'L') {sum ++;int t = mp[{i, j}];ins(S, t, 1); // 源点 -> 蜥蜴格子入点 }} for (int i = 1; i <= n; i ++) {for (int j = 1; j <= m; j ++) {int t = mp[{i, j}];ins(t, t ^ 1, h[i][j]); // 格子自身入点 -> 出点 if (jd_bon(i, j) && h[i][j]) {ins(t ^ 1, T, INF); // 能到边界的格子 -> 汇点 }}}for (int i = 1; i <= n; i ++) {for (int j = 1; j <= m; j ++) {jd_find(i, j); // 两个格子之间连边 }}int ans = 0;memset(v, 0, sizeof(v));while (spfa()) {memcpy(cur, last, sizeof(last));ans += dinic(S, INF);} cout << sum - ans << "\n"; // 总数 - 逃出去的 return 0;
}
