【题解】洛谷 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;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值