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;
}

358

被折叠的 条评论
为什么被折叠?



