POJ2195: Going Home 题解

本文介绍了一种处理费用流问题的有效算法——Edmonds-Karp结合SPFA及Dijkstra的方法,并通过引入势的概念简化了非负边权的处理。文章还提供了一个具体的实现案例,包括源代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这道题是费用流模板题

处理费用流可以用edmonds-karp+spfa算法,这样可以有效的处理负边,但spfa的复杂度不够稳定

我们可以导入势的概念从而使用dijkstra+edmonds-karp来解决费用流

设顶点i的势为h(i),我们将图中原来的边d(e)变成d'(e)=d(e)+h(u)-h(v),这样改动后的图最终的答案只要减去h(s)-h(t)就是原图的答案

所以只要合理的选取势,就可以使得所有的d'(e)都是非负数,从而可以使用djikstra()

对于h(i),可以取dist[i]作为势的值,因为在djikstra算法中有

dist[v]<=dist[u]+d(e),e是连接u和v的边

如果刚开始有负边,就先跑一趟spfa,否则可以直接跑dijkstra (注意反向边中的负边不算,因为刚开始反向边没有流量不会走到)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <utility>
#include <map>
#include <stack>
#include <set>
#include <vector>
#include <queue>
#include <deque>
#define x first
#define y second
#define mp make_pair
#define pb push_back
#define LL long long
#define Pair pair<int,int>
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+7;
const int INF=0x7ffffff;
const int magic=348;

int prevv[100048],pree[100048];
int t=0,head[100048],to[200048],nxt[200048],f[200048],w[200048],tot=1;
void addedge(int s,int t,int cap,int cost)
{
	to[++tot]=t;nxt[tot]=head[s];head[s]=tot;f[tot]=cap;w[tot]=cost;
	to[++tot]=s;nxt[tot]=head[t];head[t]=tot;f[tot]=0;w[tot]=-cost;
}

int n,m,cnt;
char a[148][148];
vector<Pair> people,house;
priority_queue<Pair> q;
int dist[100048],h[100048];

inline int myabs(int x)
{
	return x>=0?x:-x;
}

inline int calc(Pair x,Pair y)
{
	int x1=x.x,y1=x.y,x2=y.x,y2=y.y;
	return myabs(x1-x2)+myabs(y1-y2);
}

bool dijkstra()
{
	int i,x,y,dd;
	q.push(mp(0,0));
	for (i=0;i<=t;i++) dist[i]=INF;
	dist[0]=0;
	while (!q.empty())
	{
		dd=q.top().x;x=q.top().y;
		dd=-dd;q.pop();
		if (dd>dist[x]) continue;
		for (i=head[x];i;i=nxt[i])
		{
			y=to[i];
			if (f[i] && dist[y]>dist[x]+w[i]+h[x]-h[y])
			{
				dist[y]=dist[x]+w[i]+h[x]-h[y];
				prevv[y]=x;
				pree[y]=i;
				q.push(mp(-dist[y],y));
			}
		}
	}
	if (dist[t]>=INF) return false; else return true;
}

int min_cost_flow()
{
	int i,x,y,u,res,minf;
	for (i=0;i<=t;i++) h[i]+=dist[i];
	minf=INF;
	for (u=t;u;u=prevv[u])
		minf=min(minf,f[pree[u]]);
	res=minf*h[t];
	for (u=t;u;u=prevv[u])
	{
		f[pree[u]]-=minf;
		f[pree[u]^1]+=minf;
	}
	return res;
}

int main ()
{
	int i,j;
	while (scanf("%d%d",&n,&m) && n && m)
	{
		for (i=1;i<=n;i++) scanf("%s",a[i]+1);
		people.clear();house.clear();t=0;tot=1;
		for (i=1;i<=n;i++)
			for (j=1;j<=m;j++)
			{
				if (a[i][j]!='.') t++;
				if (a[i][j]=='m') people.pb(mp(i,j));
				if (a[i][j]=='H') house.pb(mp(i,j));
			}
		cnt=t/2;
		t++;
		for (i=0;i<=t;i++) head[i]=0;
		for (i=0;i<=t;i++) h[i]=0;
		for (i=1;i<=cnt;i++) addedge(0,i,1,0);
		for (i=1;i<=cnt;i++) addedge(cnt+i,t,1,0);
		for (i=0;i<people.size();i++)
			for (j=0;j<house.size();j++)
			{
				int d=calc(people[i],house[j]);
				addedge(i+1,cnt+j+1,1,d);
			}
		int ans=0;
		while (dijkstra()) ans+=min_cost_flow();
		printf("%d\n",ans);	
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值