poj2987 最大权闭合图

题意:

公司要进行裁员,裁掉一个人可以获得一定的利益或损失(用正数和负数表示)。

一些人之间有附属关系,比如b是a的下属,则裁掉a就必须裁掉b。

问要取得最大的利益,最少要裁掉几个人,取得的最大利益是多少。


思路:

先说明什么是最大权闭合图

在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。如下图:

集合{5}、{2,5}、{4,5}、{2,4,5}、{3,4,5}、{1,2,3,4,5}、{1,2,4,5} 都是闭合图,其中{3,4,5}是最大权闭合图。

如何求最大权闭合图:

1.在原图中添加s和t

2. 原图中的边权全部设为INF

3. s与原图中权值为正的点连边,权值为点的权值

4. 不满足3的点与t连边,权值为点的权值的绝对值

最大权闭合图的的权=原图中权值为正的点的和 - 最小割(最大流)

详细的内容和证明可以参考胡伯涛的论文《最小割模型在信息学竞赛中的应用》。


再回到本题,第二问求最大利益,显然就是最大权闭合图。

第一问问最少裁掉几个人,我采用的是边权放大的方法:

所有上述3中的边扩大一个很大的倍数k,再减去1,所有上述4中的边扩大同样k倍,再加上1。

设num为一共减去了多少个1,则(maxflow + num) / k仍是最大权闭合图的权,而(maxFlow + num) % k就是第一问的答案。


代码(3332K,3422MS):

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

#define T (n + 1)

const long long INF = 1e16;

using namespace std;

struct Edge{
	int from, to;
	long long cap;
	Edge() {}
	Edge(int a, int b, long long c) : from(a), to(b), cap(c) {}
};

int n, m;
vector<Edge> edges;
vector<int> G[5005];

void addEdge(int from, int to, long long cap) {
	edges.push_back(Edge(from, to, cap));
	edges.push_back(Edge(to, from, 0));
	int siz = edges.size();
	G[from].push_back(siz - 2);
	G[to].push_back(siz - 1);
}

int cur[5005];
int layer[5005];

bool build() {
	memset(layer, -1, sizeof(layer));
	queue<int> q;
	layer[0] = 0;
	q.push(0);
	while (!q.empty()) {
		int current = q.front();
		q.pop();
		for (int i = 0; i < G[current].size(); i++) {
			Edge e = edges[G[current][i]];
			if (layer[e.to] == -1 && e.cap > 0) {
				layer[e.to] = layer[current] + 1;
				q.push(e.to);
			}
		}
	}
	return layer[T] != -1;
}

long long find(int x, long long curFlow) {
	if (x == T || !curFlow) return curFlow;
	long long flow = 0, f;
	for (int &i = cur[x]; i < G[x].size(); i++) {
		Edge &e = edges[G[x][i]];
		if (layer[e.to] == layer[x] + 1
			&& (f = find(e.to, min(curFlow, e.cap)))) {
			e.cap -= f;
			edges[G[x][i] ^ 1].cap += f;
			flow += f;
			curFlow -= f;
			if (!curFlow) break;
		}
	}
	return flow;
}

long long dinic() {
	long long ans = 0;
	while (build()) {
		memset(cur, 0, sizeof(cur));
		ans += find(0, INF);
	}
	return ans;
}

int main() {
	while (~scanf("%d %d", &n, &m)) {
		for (int i = 0; i <= n; i++)
			G[i].clear();
		edges.clear();
		int a, b;
		long long x;
		long long sum = 0, num = 0;
		long long k = 6000;
		for (int i = 1; i <= n; i++) {
			cin >> x;
			if (x > 0) {
				sum += x;
				num++;
				addEdge(0, i, (long long)x * k - 1);
			} else addEdge(i, T, (long long)-x * k + 1);
		}
		for (int i = 0; i < m; i++) {
			scanf("%d %d", &a, &b);
			addEdge(a, b, INF);
		}
		long long maxFlow = dinic();
		cout << (maxFlow + num) % k << ' ' << sum - (maxFlow + num) / k << endl;
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值