【最大流 最小割】JZOJ_3348 秘密任务

题意

在一张图上,去掉最短路径上的一些边,使得最短路不连通,判断方案是否唯一以及求出最小去掉边的边权总和。

思路

首先把最短路径上的点取出来。

去掉边不连通显然是求最小割,之后再判断最小割是否唯一。

在残量网络中,边 ( u , v ) (u,v) (u,v) u ∈ S u\in S uS v ∈ T v\in T vT,那么这条边是必割边。
判断一下必割边的容量总和与最小割。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>

const long long N = 4501, M = 8001, inf = 1 << 29;
int n, m, tot, totn, s, t, mid;
long long ans;
long long a[N], edge[M], edgen[M], dis1[N], dis2[N];
int arrs[N], arrt[N];
int ver[M], head[N], next[M];
int vern[M], headn[N], nextn[M];
int v[N], dep[N];

void add(int u, int v, long long w) {
	ver[++tot] = v;
	edge[tot] = w;
	next[tot] = head[u];
	head[u] = tot;
	ver[++tot] = u;
	edge[tot] = w;
	next[tot] = head[v];
	head[v] = tot;
}

void addn(int u, int v, long long w) {
	vern[++totn] = v;
	edgen[totn] = w;
	nextn[totn] = headn[u];
	headn[u] = totn;
	vern[++totn] = u;
	edgen[totn] = 0;
	nextn[totn] = headn[v];
	headn[v] = totn;
}

void spfa1() {
	memset(dis1, 127 / 3, sizeof(dis1));
	memset(v, 0, sizeof(v));
	std::queue<int> q;
	q.push(1);
	dis1[1] = 0;
	v[1] = 1;
	while (q.size()) {
		int x = q.front();
		q.pop();
		v[x] = 0;
		for (int i = head[x]; i; i = next[i])
			if (dis1[ver[i]] > dis1[x] + edge[i]) {
				dis1[ver[i]] = dis1[x] + edge[i];
				if (!v[ver[i]]) {
					q.push(ver[i]);
					v[ver[i]] = 1;
				}
			}
	}
}

void spfa2() {
	memset(dis2, 127 / 3, sizeof(dis2));
	memset(v, 0, sizeof(v));
	std::queue<int> q;
	q.push(n);
	dis2[n] = 0;
	v[n] = 1;
	while (q.size()) {
		int x = q.front();
		q.pop();
		v[x] = 0;
		for (int i = head[x]; i; i = next[i])
			if (dis2[ver[i]] > dis2[x] + edge[i]) {
				dis2[ver[i]] = dis2[x] + edge[i];
				if (!v[ver[i]]) {
					q.push(ver[i]);
					v[ver[i]] = 1;
				}
			}
	}
}

int bfs() {
	memset(dep, 0, sizeof(dep));
	std::queue<int> q;
	dep[s] = 1;
	q.push(s);
	while (q.size()) {
		int x = q.front();
		q.pop();
		for (int i = headn[x]; i; i = nextn[i])
			if (edgen[i] && !dep[vern[i]]) {
				dep[vern[i]] = dep[x] + 1;
				if (vern[i] == t) return 1;
				q.push(vern[i]);
			}
	}
	return 0;
}

long long dfs(int p, long long flow) {
	if (p == t) return flow;
	long long rest = flow;
	for (int i = headn[p]; i && rest; i = nextn[i]) {
		if (dep[vern[i]] == dep[p] + 1 && edgen[i]) {
			long long k = dfs(vern[i], std::min(rest, edgen[i]));
			if (!k) dep[vern[i]] = 0;
			edgen[i] -= k;
			edgen[i ^ 1] += k;
			rest -= k;
		}
	}
	return flow - rest;
}

void dinic() {
	long long flow = 0;
	while (bfs())
		while (flow = dfs(s, inf)) ans += flow;
}

void dfss(int p) {
	arrs[p] = 1;
	for (int i = headn[p]; i; i = nextn[i])
		if (!arrs[vern[i]] && edgen[i]) dfss(vern[i]);
}

void dfst(int p) {
	arrt[p] = 1;
	for (int i = headn[p]; i; i = nextn[i])
		if (!arrt[vern[i]] && edgen[i ^ 1]) dfst(vern[i]);
}

int main() {
	int T;
	scanf("%d", &T);
	for (; T; T--) {
		scanf("%d %d", &n, &m);
		tot = 1;
		totn = 1;
		s = 1;
		t = n;
		ans = 0;
		memset(head, 0, sizeof(head));
		memset(headn, 0, sizeof(headn));
		memset(arrs, 0, sizeof(arrs));
		memset(arrt, 0, sizeof(arrt));
		for (long long i = 1; i < n; i++)
			scanf("%d", &a[i]);
		a[n] = inf;
		for (long long i = 1, x, y, z; i <= m; i++) {
			scanf("%d %d %lld", &x, &y, &z);
			add(x, y, z);
	 	}
	 	spfa1();
	 	spfa2();
	 	mid = n;
		for (long long i = 2; i <= tot; i++) {
			long long x = ver[i ^ 1], y = ver[i];
			if (dis1[x] + dis2[x] != dis1[n] || dis1[y] + dis2[y] != dis1[n]) continue;
			if (dis1[x] + edge[i] == dis1[y]) {
				addn(x, ++mid, a[x]);
				addn(mid, y, a[y]);
			}
		}
		dinic();
		dfss(s);
		dfst(t);
		long long res = 0;
		for (int i = 1; i <= mid; i++) if (arrs[i])
			for (int j = headn[i]; j; j = nextn[j])
				if (arrs[i] && arrt[vern[j]])
					res += edgen[j ^ 1];
		res == ans ? printf("Yes ") : printf("No ");
		printf("%lld\n", ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值