POJ 2195 Going Home 最小费用最大流(模板题)


以下来自点击打开链接

最小费用最大流

    通过EK,Dinic,ISAP算法可以得到网络流图中的最大流,一个网络流图中最大流的流量max_flow是唯一的,但是达到最大流量max_flow时每条边上的流量分配f是不唯一的。 
    如果给网络流图中的每条边都设置一个费用cost,表示单位流量流经该边时会导致花费cost。那么在这些流量均为max_flow的流量分配f中,存在一个流量总花费最小的最大流方案。 
 min{sum(cost(i, j)*f(i,j) | (i, j)属于方案f中的边, f(i,j)为 边(i,j)上的流量, f为某一个最大流方案}。此即为最小费用最大流

算法思想

    采用贪心的思想,每次找到一条从源点到达汇点的路径,增加流量,且该条路径满足使得增加的流量的花费最小,直到无法找到一条从源点到达汇点的路径,算法结束。 
    由于最大流量有限,每执行一次循环流量都会增加,因此该算法肯定会结束,且同时流量也必定会达到网络的最大流量;同时由于每次都是增加的最小的花费

由归纳法得:当前的最小花费是所有到达当前流量flow时的花费最小值,因此最后的总花费最小。



点击打开链接

题意:n*m地图 n,m<=100,地图中有房子'H'和人'M',每个人能向4个方向移动一个单位,花费1.
每个房子只能住一个人 问把'M'个人移动到'H'个房子中的最小花费?
建图 顶点为:M个人,H个房子,源点S和T 人和房子间连有向边容量为1,费用为曼哈顿距离,源点和人,房子和汇点的容量为1,费用为0
则M个人都要进H个房子的最小费用为:流量为最大流时的最小费用 

#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<int,int> ii;
const int N=3e2+20;
const int inf=2e8;
int n,m;
struct Edge{
	int to,vol,cost,next;
}e[N*N];
int head[N],pre[N],path[N],dist[N];
int num;//边
void insert(int u,int v,int vol,int cost)
{
	e[num].to=v,e[num].vol=vol,e[num].cost=cost;
	e[num].next=head[u];
	head[u]=num++;
	
	e[num].to=u,e[num].vol=0,e[num].cost=-cost;//反向边 减少容量,则价格也减少
	e[num].next=head[v];
	head[v]=num++;	
} 
queue<int> q;
int inq[N];
bool SPFA(int s,int t)
{
	while(!q.empty())
		q.pop();
	memset(pre,-1,sizeof(pre));
	memset(dist,0x7f,sizeof(dist));
	memset(inq,0,sizeof(inq));
	dist[s]=0;
	inq[s]=1;
	q.push(s);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		inq[u]=0;
		for(int p=head[u];p!=-1;p=e[p].next)
		{
			int v=e[p].to;
			if(e[p].vol>0 &&dist[u]+e[p].cost<dist[v])
			{
				dist[v]=dist[u]+e[p].cost;
				pre[v]=u,path[v]=p;//前一条边和点 
				if(!inq[v])
					inq[v]=1,q.push(v);
			}
		}
	}
	if(pre[t]==-1)
		return false;
	return true;
}
int Min_CostFlow(int s,int t)
{
	int cost=0,flow=0;
	while(SPFA(s,t))
	{
		int f=inf;
		for(int u=t;u!=s;u=pre[u])
			f=min(f,e[path[u]].vol);
		flow+=f,cost+=dist[t]*f;
		for(int u=t;u!=s;u=pre[u])
		{
			e[path[u]].vol-=f;
			e[path[u]^1].vol+=f;//残余网络 
		}
	}
	return cost;
}
char s[N][N];
vector<ii> p1,p2;
int cnt;
void init()
{
	int off=cnt;
	for(int i=0;i<p1.size();i++)
		insert(0,i+1,1,0);
	for(int j=0;j<p2.size();j++)
		insert(j+1+off,2*cnt+1,1,0);
		
	for(int i=0;i<p1.size();i++)
	{	
		for(int j=0;j<p2.size();j++)
		{
			int d=abs(p1[i].first-p2[j].first)+abs(p1[i].second-p2[j].second);	
			insert(i+1,j+1+cnt,1,d);
		}
	}
}
int main()
{
	while(cin>>n>>m&&(n+m))
	{
 		memset(head,-1,sizeof(head));
		num=cnt=0;
		p1.clear(),p2.clear();
		for(int i=1;i<=n;i++)
			scanf("%s",s[i]+1);	
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				if(s[i][j]=='m')
					p1.push_back(ii(i,j)),cnt++;
				if(s[i][j]=='H')
					p2.push_back(ii(i,j));
			}
		}	
		init();
		int ans=Min_CostFlow(0,2*cnt+1);
		cout<<ans<<endl;	
	}	 
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值