P4311 士兵占领 题解 - 上下界网络流

看到这种最小方案分配的问题,先考虑动态规划或者是贪心,发现不可以解决。于是考虑图上算法——网络流。

很显然会想到将每个行变成一个点,将每一个列变成一个点。(如果这个 trick 不会也没有关系,以后就要会了)

然后考虑源点和汇点。使用源点来向各个行来发送士兵,使用汇点从各个列来聚集士兵。因为在任何时候 所有行的士兵数的和 等于所有列的士兵数的和。

考虑每一条边的容量。

从源点到行的容量很显然是这样子的:下界是输入给定的至少要放的士兵数,上界是这一行除了那些不能放士兵的格子以外还能放多少。

从行 x x x 到列 y y y 的容量是这样子的:下界为 0 0 0。如果 ( x , y ) (x,y) (x,y) 能放士兵上界就是 1 1 1,否则是 0 0 0

从列到汇点的容量是这样子的:下界是输入给定的至少要放的士兵数,上界是这一列除了那些不能放士兵的格子以外还能放多少。

于是就连边求最小流,就结束了。

当前弧优化的数组一定要清空啊啊啊啊啊啊!!!

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m, k;
const int N = 210;
int a[N], b[N];

struct edge {
	int to, val;
	int id;
};
vector<edge> v[N];
int d[N];
int cur[N];
int sum[N], cnt1[N], cnt2[N];
bool is[N][N];

void bfs(int s) {
	memset(d, -1, sizeof d);
	queue<int> q;
	q.push(s), d[s] = 0;
	while (!q.empty()) {
		int u = q.front();
//		cout << u << endl;
		q.pop();
		for (auto [to, val, id] : v[u])
			if (d[to] == -1 && val > 0)
				d[to] = d[u] + 1, q.push(to);
	}
}

int dfs(int u, int t, int fl) {
	if (u == t)
		return fl;
	for (int i = cur[u]; i < (int)v[u].size(); i = ++cur[u])
		if (d[v[u][i].to] == d[u] + 1 && v[u][i].val > 0) {
			int f = dfs(v[u][i].to, t, min(fl, v[u][i].val));
			if (f > 0) {
				v[u][i].val -= f, v[v[u][i].to][v[u][i].id].val += f;
				return f;
			} else
				d[v[u][i].to] = -1;
		}
	return 0;
}

int dinic(int s, int t) {
	int ans = 0;
	while (1) {
		bfs(s);
		if (d[t] == -1)
			return ans;
		int x = 0;
		memset(cur, 0, sizeof cur);
		while ((x = dfs(s, t, 9e18)) > 0)
			ans += x;//, cout << ans << endl;
	}
	return ans;
}

void add(int x, int y, int dn, int up) {
	if (dn > up) {
		cout << "JIONG!";
		exit(0);
	}
	v[x].push_back({y, up - dn, (int)v[y].size()});
	v[y].push_back({x, 0, (int)v[x].size() - 1});
	sum[x] += dn, sum[y] -= dn;
}

signed main() {
	cin >> n >> m >> k;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	for (int i = 1; i <= m; i++)
		cin >> b[i];
	for (int i = 1; i <= k; i++) {
		int x, y;
		cin >> x >> y;
		cnt1[x]++, cnt2[y]++, is[x][y] = 1;
	}
	int s = m + n + 1, t = m + n + 2;
	for (int i = 1; i <= n; i++)
		add(s, i, a[i], m - cnt1[i]);
	for (int i = 1; i <= m; i++)
		add(i + n, t, b[i], n - cnt2[i]);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			add(i, j + n, 0, 1 - is[i][j]);
	add(t, s, 0, 9e18);
	int su = 0;
	for (int i = 1; i <= n + m + 2; i++)
		if (sum[i] > 0)
			add(i, m + n + 4, 0, sum[i]), su += sum[i];
		else if (sum[i] < 0)
			add(m + n + 3, i, 0, -sum[i]);
//	cout << su << " " << dinic(n + m + 3, n + m + 4) << endl;
	if (su != dinic(n + m + 3, n + m + 4))
		cout << "JIONG!\n";
	else {
		int ans = 0;
		for (auto &[to, val, id] : v[s])
			if (to == t)
				ans = max(ans, val);
//		cout << ans << endl << endl;
		for (auto &[to, val, id] : v[t])
			if (to == s && val > 1e13)
				val = 0, v[s][id].val = 0;
		cout << ans - dinic(t, s) << endl;
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值