hdu 4411 Arrest(费用流)


题意:有N+1个点,每个点与编号大于自己的点之间有一条有权边(权重通过floyd求得),现有k个人位于0处,要从k个人中选出若干个人遍历其它点并最终回到0点,使每个点(除0外)都被访问恰好一次,问最小费用之和为多少

解法:每个点之多走一次,显然需要把一个点拆成两个,一个出点一个入点之间费用为0流量为1,超级源点拆为流量为k费用为距离的边,但如何让保证每个点都恰好走过一次呢?由于原图无环,所以可以将i和i‘之间的费用设为-M,流量设为1,M应该大于源点和汇点间最长链的长度。由于每次都找最短路径,因此这些边一定会被有限考虑,因此可以保证这些边恰好走了一次。

但是每次次增广后都会产生反向负权边,如何保证不走反向边呢?方法是源点和汇点之间连一条流量为k费用为0的点(貌似也可以枚举k)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
#define maxn 1010
#define maxm 20010
const int inf = 0x3f3f3f3f;
struct Nod {
	int b, nxt;
	int cap, cst;
	void init(int b, int nxt, int cap, int cst) {
		this->b = b;
		this->nxt = nxt;
		this->cap = cap;
		this->cst = cst;
	}
};
struct MinCost {
	int E[maxn];
	int n;
	Nod buf[maxm * 2];
	int len;
	int p[maxn];
	void init(int n) {
		this->n = n;
		memset(E, 255, sizeof(E));
		len = 0;
	}
	void addCap(int a, int b, int cap, int cst) {
	//	printf("%d %d %d\n",a,b,cst);
		buf[len].init(b, E[a], cap, cst);
		E[a] = len++;
		buf[len].init(a, E[b], 0, -cst);
		E[b] = len++;
	}
	bool spfa(int source, int sink) {
		static queue<int> q;
		static int d[maxn];
		memset(d, 63, sizeof(d));
		memset(p, 255, sizeof(p));
		d[source] = 0;
		q.push(source);
		int u, v;
		while (!q.empty()) {
			u = q.front();
			q.pop();
			for (int i = E[u]; i != -1; i = buf[i].nxt) {
				v = buf[i].b;
				if (buf[i].cap > 0 && d[u] + buf[i].cst < d[v]) {
					d[v] = d[u] + buf[i].cst;
					p[v] = i;
					q.push(v);
				}
			}
		}
		return d[sink] != inf;
	}
	int solve(int source, int sink) {
		int minCost = 0, maxFlow = 0;//需要maxFlow的话,想办法返回
		while (spfa(source, sink)) {
			int neck = inf;
			for (int t = p[sink]; t != -1; t = p[buf[t ^ 1].b])//buf[t^壹].b是父节点
				neck = min(neck, buf[t].cap);
			maxFlow += neck;
			for (int t = p[sink]; t != -1; t = p[buf[t ^ 1].b]) {
				buf[t].cap -= neck;
				//printf("%d\n",buf[t].b);
				buf[t ^ 1].cap += neck;
				minCost += buf[t].cst * neck;
			}
			//printf("-----\n");
		}
		return minCost;
	}
} mc;
int map[110][110],n;
void floyd() {
	for (int k =0; k <= n; k++)
		for (int i =0; i <= n; i++)
			for (int j=0; j <= n; j++){
				if (map[i][k] + map[k][j] < map[i][j])
					map[i][j] = map[i][k]+map[k][j];
			}
}
int main() {
	int m, k, a, b, c;
	while (scanf("%d%d%d",&n,&m,&k)&&n) {
		for (int i = 0; i <= n; i++)
			for (int j = 0;j<= n; j++)
				map[i][j] = inf;
		while (m--) {
			scanf("%d%d%d", &a, &b, &c);
			map[b][a]=map[a][b] = min(map[a][b],c);
		}
		floyd();
		mc.init(n * 2 + 3);
		mc.addCap(0, n * 2 + 1, k, 0);
		mc.addCap(n*2+1,n*2+2,k,0);
		int temp=1<<23;
		for (int i = 1; i <= n; i++) {
			mc.addCap(i, i + n,1,-temp);
			mc.addCap(n*2+1,i,1,map[i][0]);
			mc.addCap(i+n, n*2+2,1,map[i][0]);
			for (int j = i+1;j<=n;j++)
				mc.addCap(i+n,j,1,map[i][j]);
		}
		printf("%d\n",mc.solve(0,n*2+2)+temp*n);
	}
	return 0;
}

博客搬新家: sensirly.github.io

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值