2325. 有向图破坏(二分图,最小点权覆盖,最小割,网络流)

活动 - AcWing

爱丽丝和鲍勃正在玩以下游戏。

首先,爱丽丝绘制一个 N 个点 M 条边的有向图。

然后,鲍勃试图毁掉它。

在每一步操作中,鲍勃都可以选取一个点,并将所有射入该点的边移除或者将所有从该点射出的边移除。

已知,对于第 i 个点,将所有射入该点的边移除所需的花费为 W+i,将所有从该点射出的边移除所需的花费为 W−i。

鲍勃需要将图中的所有边移除,并且还要使花费尽可能少。

请帮助鲍勃计算最少花费。

输入格式

第一行包含 N 和 M。

第二行包含 N 个正整数,第 i 个为 Wi+。

第三行包含 N 个正整数,第 i 个为 Wi−。

接下来 M 行,每行包含两个整数 a,b,表示从点 a 到点 b 存在一条有向边。

所有点编号从 1 到 N。

图中可能由重边或自环。

输出格式

第一行输出整数 W,表示鲍勃所需的最少花费。

第二行输出整数 K,表示鲍勃需要进行的操作步数。

接下来 K 行,每行输出一个鲍勃的具体操作。

如果操作为将所有射入点 i 的边移除,则输出格式为 i +

如果操作为将所有从点 i 射出的边移除,则输出格式为 i -

如果答案不唯一,则输出任意一种即可。

数据范围

1≤N≤100
1≤M≤5000
1≤W+i,W−i≤106

输入样例:
3 6
1 2 3
4 2 1
1 2
1 1
3 2
1 2
3 1
2 3
输出样例:
5
3
1 +
2 -
2 +

解析: 

 点覆盖集:

对于一个无向图,如果存在一个点覆盖集,使得图中的每条边都至少有一个端点在这个集合中,那么这个集合就是图的点覆盖集。点覆盖集的大小就是集合中包含的顶点数目。

最小权点覆盖集(Minimum Weight Vertex Cover)是指在一个带权图中,找到一个顶点集合,使得这个集合中的顶点能够覆盖图中的所有边,并且集合中顶点的权重之和最小

如何求最小权点覆盖集:

最小权点覆盖集问题在一般的图中是一个 NP 完全问题(NPC 问题),目前证明出来只能用爆搜来做,时间复杂度一定是指数级别。

但是在二分图中存在一种非常高效的做法。

首先对于二分图中,如果所有点的权值都是 1,那么久可以用匈牙利算法直接求,且最大匹配数 = 最小点覆盖数 = n− 最大独立集数。

如果所有点的权值不一定是 1,那么就不能用匈牙利算法来求,只能用网络流去解决这类问题。

那么我们如何用网络流来解决一个点权任意且非负的最小权点覆盖集的问题呢?

对于一个二分图,应该有两排节点,另有一个源点一个汇点,两排节点之间存在若干条边,每条边都至少要覆盖一个点,因此可以将这些边的容量设置为 +∞,保证割边不出现在中间。对于每条边来说,这两个点至少要有一个被选,类比到流网络中相当于是这两个点和源点、汇点之间的边要是割边,而最小权点覆盖集和最小割都是求最小值,因此我们就可以往这个方面去考虑。

接下来需要具体证明能否用最小割模型解决二分图的最小权点覆盖集问题,每个点都有点权,左排节点的权值可以放到从源点到这些点的边上,而右排节点的权值可以放到从这些点到汇点的边上。由于点权是非负的,所以容量是合法的。中间所有边的容量同以上思路设置为 +∞。

然后我们可以看一下所建的流网络和原问题的点覆盖的集合之间是否存在关系,首先定义一个概念,将所有只包含和源点、汇点相连的边作为割边的割称为简单割(仅限于本题)。显然,简单割是所有割的一个子集。

可以发现,最小割一定是一个简单割,因为最小割就是最大流,而从源点出发的所有边都是有限容量,所以最大流是有限值,最大流等同于最小割也是有限值,因此最小割一定不包含中间那些容量是 +∞ 的边,所以最小割一定是简单割

对于流网络中任意一个简单割 [S,T],如果割边是和源点相连,则将对应的左排节点加入点覆盖集,如果割边是和汇点相连,则将对应的右排节点加入点覆盖集。这样选出来的点集一定是原图的一个点覆盖集,即保证每条边至少选中一个点。如何证明呢,如果某一条边的两个点都不在点覆盖集中,那么说明两个点和源点、汇点之间的边都不是割边,这样就能通过这一条路径从源点搜到汇点,这就不是一个简单割了,由此反证得出每条边的两个点至少有一个点被选中。这样我们就能将任意一个简单割对应到任意一个点覆盖集。

对于任意一个点覆盖集,和上面通过简单割构造点集的方法相反。这里枚举点覆盖集中的所有点,如果该点是左排节点,则从源点到该点的边设为割边,如果该点是右派节点,则从该点到汇点的边设为割边。然后从源点往下搜索,搜到的所有点放到 S 集合中,搜不到的所有点放到 T 集合中。如此得到一个简单割,首先所有割边必然在左、右两侧,所以如果是割就一定是简单割,那么是不是一个合法的割呢,只需要看能都从源点走到汇点即可,这里假设从源点能走到汇点,那么就必然经过了中间某一条边,说明这条边的两个点都没有在点覆盖集中,这就不是一个合法的点覆盖集,就矛盾了,由此反证得出从源点一定走不到汇点,所以构造出的是合法的简单割。

综上所述,证明了任意一个简单割的集合和任意一个原问题的点覆盖集是一一对应的,然后需要看一下它们之间的数量关系,可以发现任意一个简单割的容量就是它所有割边的容量之和,由于已经设置这些边的容量就是对应点的权值,所以选出的割边的容量之和就等价于原图中选出的三个点的权值之和,就是对应的点覆盖集的权值之和。所以两者的关系是相等的关系。

到此为止,总结了求最小权点覆盖集的整个思路,按照指定的建图方式求一遍最小割就是最小权点覆盖集的权值之和。

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<math.h>
#include<map>
#include<sstream>
#include<deque>
#include<unordered_map>
#include<unordered_set>
#include<bitset>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
const int N = 2e2 + 10, M = (5e3 + N * 2) * 2+10, INF = 0x3f3f3f3f;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
bool st[N];

void add(int a, int b, int c) {
	e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
	e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}

bool bfs() {
	int hh = 0, tt = 0;
	memset(d, -1, sizeof d);
	q[0] = S, d[S] = 0, cur[S] = h[S];
	while (hh <= tt) {
		int t = q[hh++];
		for (int i = h[t]; i != -1; i = ne[i]) {
			int j = e[i];
			if (d[j] == -1 && f[i]) {
				d[j] = d[t] + 1;
				cur[j] = h[j];
				if (j == T)return 1;
				q[++tt] = j;
			}
		}
	}
	return 0;
}

int find(int u, int limit) {
	if (u == T)return limit;
	int flow = 0;
	for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
		int j = e[i];
		cur[u] = i;
		if (d[j] == d[u] + 1 && f[i]) {
			int t = find(j, min(f[i], limit - flow));
			if (!t)d[j] = -1;
			f[i] -= t, f[i ^ 1] += t, flow += t;
		}
	}
	return flow;
}

int dinic() {
	int ret = 0, flow;
	while (bfs())while (flow = find(S, INF))ret += flow;
	return ret;
}

void dfs(int u) {
	st[u] = 1;
	for (int i = h[u]; i != -1; i = ne[i]) {
		int j = e[i];
		if (!st[j] && f[i]) {
			dfs(j);
		}
	}
}

int main() {
	cin >> n >> m;
	memset(h, -1, sizeof h);
	S = 0, T = 2 * n + 1;
	for (int i = 1, a; i <= n; i++) {
		scanf("%d", &a);
		add(S, i, a);
	}
	for (int i = 1,a; i <= n; i++) {
		scanf("%d", &a);
		add(i + n, T, a);
	}
	for (int i = 1, a, b; i <= m; i++) {
		scanf("%d%d", &a, &b);
		add(b, a + n, INF);
	}
	printf("%d\n", dinic());
	dfs(S);
	//cout << "________________________________ " << endl;
	int k = 0;
	for (int i = 0; i < idx; i += 2) {
		int a = e[i], b = e[i ^ 1];
		if (!st[a] && st[b])k++;
	}
	cout << k << endl;
	for (int i = 0; i < idx; i += 2) {
		int a = e[i], b = e[i ^ 1];
		if (!st[a] && st[b]) {
			if (b == S)printf("%d +\n", a);
		}
	}
	//cout << "++++++++++++++++++++++++ " << endl;
	for (int i = 0; i < idx; i += 2) {
		int a = e[i], b = e[i ^ 1];
		if (!st[a] && st[b]) {
			if (a == T)printf("%d -\n", b-n);
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值