hdu 4067 Random Maze(最小费用最大流)

http://blog.acmj1991.com/?p=795

题意:给出一个有n个点m条边的有向图,现在要从图中删去一些边使得图满足以下条件:

1.图中只有一个入口(s)和一个出口(t)
2.所以边都是单向的(这个原图已经保证了)
3.对入口(s)而言,其出度=入度+1
4.对出口(t)而言,其入度=出度+1
5.对入口(s)、出口(t)以外的点,其入度=出度
而对图中每条边,保留这条边或者删去这条边都有相应的花费,分别为a、b,求使得该图满足条件的最小花费。

思路:【转载 starvae】(1).题目的意思是在一个有向图中选择一些边使得每个点的入度出度相同, 出了起点和终点, 我们在起点和终点中加上一条终点指向起点的边就可以了。

我们给每条边定义新的权值, 大小为a-b, 这样我们的任务就转变成在新的图中寻找所有的负环, 然后将它转向为正环, 直到找到所有的负环。 还有一个要求是寻找到的负环中的其中一个必须包含新加上的终点指向起点的边。 这样就能保证起点和终点的度的特殊性。

如果没有包含这条边的负环, 那么就是impossible。用spfa就能解决, 但是速度慢,并且因为有重边的缘故, 判断负环的时候要特别注意一点。 我写了个用了4406 MS,时限给了3S, 如果有人能用SPFA过, 那着实仰慕。

(2).以上是最直接的方法.
下面是直接建图的方法。
我们的答案保存在sum中。
初始时每个点的in[] out[] 都为0, in[i] 表示第i这个点需要in[i]条指向i的边才能满足i这个点的入度平衡。
对于每条边,如果a <= b 那么建 v->u的边,流量为1, 权值为b-a。 sum+= a;
此时, u->v 被翻转, 所以in[v] ++, out[u] ++.
否则 建 u->v的边, 流量为1, 权值为a-b。 sum += b;
此时, u->v 没有被翻转, 所以in[] out[] 不改变
然后添加一条终点指向起点的边, 直接in[s] ++; out[t] ++;
然后新建超级源汇S,T。
对于每个点i, 如果in[i] > out[i] . 建边S->i, 权值为0, 流量为in[i] – out[i];
否则的话 建边 i->T ,权值为0, 流量为out[i] – in[i];
然后跑一次最小费用最大流。
如果最后从S出去的边没有满流的话 就是impossible。

否则答案即为sum+ mincost.

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;

#define maxN 1010
#define maxM 400100
#define A 400000
#define inf 0x0fffffff
int in[maxN],out[maxN],head[maxN];
int vist[maxN],dis[maxN],pre[maxN],stack[maxM];
int num;
struct node{
	int v,f,cost,next;
}po[maxM];

void add(int u,int v,int f,int cost)
{
	po[num].v=v,po[num].f=f,po[num].cost=cost;
	po[num].next=head[u],head[u]=num++;
	po[num].v=u,po[num].f=0,po[num].cost=-cost;
	po[num].next=head[v],head[v]=num++;
}
void init()
{
	num=0;
	memset(in,0,sizeof(in));
	memset(out,0,sizeof(out));
	memset(head,-1,sizeof(head));
}
int SPFA(int s,int n)
{
	memset(pre,-1,sizeof(pre));
	memset(vist,0,sizeof(vist));
	for(int i=0;i<=n;i++)
		dis[i]=inf;
	dis[s]=0,vist[s]=1;
	int tail=0,hea=0,u;
	stack[tail++]=s;
	while(hea!=tail)
	{
		vist[u=stack[hea++]]=0;hea%=A;
		for(int i=head[u],v=po[i].v;i!=-1;i=po[i].next,v=po[i].v){
			if(dis[v]>dis[u]+po[i].cost&&po[i].f)
			{
				pre[v]=i;
				dis[v]=dis[u]+po[i].cost;
				if(!vist[v])
				{
					vist[v]=1;
					stack[tail++]=v;tail%=A;
				}
			}
		}
	}
	if(dis[n]==inf)return false;
	else return true;
}
int solve(int s,int n)
{
	int resf=0,resc=0,m;
	while(SPFA(s,n))
	{
		int minn=inf;
		for(int i=pre[n];i!=-1;i=pre[po[i^1].v])
			minn=min(minn,po[i].f);
		resc+=minn*dis[n];
		for(int i=pre[n];i!=-1;i=pre[po[i^1].v])
			po[i].f--,po[i^1].f++;
	}
	if(head[s]==-1)return -1;
	for(int i=head[s];i!=-1;i=po[i].next)
		if(po[i].f!=0)
			return -1;
	return resc;
}

int main()
{
	int n,m,s,t,h;
	scanf("%d",&h);
	for(int l=1;l<=h;l++)
	{
		init();
		int u,v,a,b,S,T,sum=0;
		scanf("%d%d%d%d",&n,&m,&s,&t);
		S=0,T=n+1;
		while(m--){
			scanf("%d%d%d%d",&u,&v,&a,&b);
			if(a<=b){
				in[v]++,out[u]++;
				add(v,u,1,b-a);
				sum+=a;
			}else{
				add(u,v,1,a-b);
				sum+=b;
			}
		}	
		in[s]++,out[t]++;
		for(int i=1;i<=n;i++)
		{
			if(out[i]<in[i])
				add(S,i,in[i]-out[i],0);
			else
				add(i,T,out[i]-in[i],0);
		}
		int temp=solve(S,T);
		printf("Case %d: ",l);
		if(temp!=-1)printf("%d\n",sum+temp);
		else printf("impossible\n");
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值