poj 2195 Going Home--最小费用最大流--spfa--动态数组--或者用 最小权匹配

/*
题意:给定一个N*M的地图,地图上有若干个man和house,且man与house的数量一致。man每移动一格需花费$1(即单位费用=单位距离),
	  一间house只能入住一个man。现在要求所有的man都入住house,求最小费用。
最小费用最大流  用到了spfa求费用最小的可改进路径
仿写 http://blog.csdn.net/lyy289065406/article/details/6732762 这个的,这儿有题解和注释,几乎完全一样,哈哈,学习了。
*/
#include<iostream>
#include<cmath>
#include<queue>
using namespace std;
class coordinate
{
public:
	int x,y;
	coordinate()
	{}
};
class solve
{
public:
	solve(int row,int col):r(row),c(col)
	{
		int i,j,mzz,hzz;
		mannum=0;
		MinCost=0;
		map=new char*[r];
		for(i=0;i<r;i++)
		{
			map[i]=new char[c];
			for(j=0;j<c;j++)
			{
				cin>>map[i][j];
				if(map[i][j]=='m')
					++mannum;
			}
		}
		//找到人和家的坐标
		man=new coordinate[mannum+1];
		house=new coordinate[mannum+1];
		mzz=hzz=0;

		for(i=0;i<r;++i)
			for(j=0;j<c;++j)
				if(map[i][j]=='m')
				{
					man[++mzz].x=i;
					man[mzz].y=j;
				}else if(map[i][j]=='H')
				{
					house[++hzz].x=i;
					house[hzz].y=j;
				}
		//建 花费图 和 流图
		cost=new int*[2*mannum+2];
		flow=new int*[2*mannum+2];
		for(i=0;i<2*mannum+2;++i)
		{
			cost[i]=new int[2*mannum+2];
			flow[i]=new int[2*mannum+2];

			memset(cost[i],0,sizeof(int)*(2*mannum+2));//注意哦   跟以往的写法不太一样
			memset(flow[i],0,sizeof(int)*(2*mannum+2));
		}
		//初始化
		s=0;
		t=2*mannum+1;
		for(i=1;i<=mannum;++i)
			flow[s][i]=1;
		for(i=1;i<=mannum;++i)
			for(j=1;j<=mannum;++j)
			{
				cost[i][j+mannum]=abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y);
				cost[j+mannum][i]=-cost[i][j+mannum];
				flow[i][j+mannum]=1;
			}
		for(i=mannum+1;i<t;++i)
			flow[i][t]=1;
		//求解
		pre=new int[2*mannum+2];
		memset(pre,0,sizeof(int)*(2*mannum+2));
		dist=new int[2*mannum+2];
		vis=new int[2*mannum+2];
		while(spfa())//还有增广路径
			add();//压入流
	}
	int inf() const{return 0x7FFFFFFF;}//设最大值
	int min(int a,int b) {return a<b?a:b;}//取较小的
	int spfa()//求费用最小的改进路径
	{
		dist[s]=0;
		for(int i=1;i<2*mannum+2;i++)
			dist[i]=inf();

		memset(vis,0,sizeof(int)*(2*mannum+2));
		vis[s]=1;

		queue<int>q;
		q.push(s);

		while(!q.empty())
		{
			int u=q.front();
			for(int v=0;v<=t;++v)
			{
				if(flow[u][v]&&dist[v]>dist[u]+cost[u][v])
				{
					dist[v]=dist[u]+cost[u][v];
					pre[v]=u;

					if(!vis[v])
					{
						q.push(v);
						vis[v]=1;
					}
				}
			}
			q.pop();
			vis[u]=0;
		}
		if(dist[t]==inf())
			return 0;
		return 1;
	}
	void add()//压入流
	{
		int MaxFlow=inf();  
		int i;
	
		for(i=t;i!=s;i=pre[i]) 
			MaxFlow=min(MaxFlow,flow[pre[i]][i]);
		
		for(i=t;i!=s;i=pre[i])
		{
			flow[pre[i]][i]-=MaxFlow;
			flow[i][pre[i]]+=MaxFlow;
			MinCost+=cost[pre[i]][i]*MaxFlow;
		}
	
		return;
	}
	~solve()//
	{
		cout<<MinCost<<endl;//输出解
		delete[] man;//收回空间
		delete[] house;
		delete[] dist;
		delete[] vis;
		delete[] pre;
		for(int i=0;i<r;++i)
			delete[] map[i];
		for(int j=0;j<2*mannum+2;++j)
		{
			delete[] cost[j];
			delete[] flow[j];
		}
		delete[] map;
		delete[] cost;
		delete[] flow;
	}
private:
	int MinCost;
	int r,c;
	int** cost,**flow;
	int mannum;
	char** map;
	coordinate *man;
	coordinate *house;
	int s,t;
	int* pre,*vis,*dist;
};
int main()
{
	int row,col;
	while(cin>>row>>col,row+col)
		solve poj2195(row,col);
	return 0;
}

在求最短路的时候,需要判断 是否还有流可加,更新距离的时候依据的是cost


这题也可以用最小权匹配来做,学了二分图之后,用最小权匹配又写了一遍


/*
poj 2195 Going Home

学习了二分图匹配,所以又用 最小权匹配 些了一边,比 最小费用最大流 快了一些

最小权匹配  与最大权匹配差不多

只需要把  边的权取相反数

然后过程跟最大权匹配一样

最后 边权和 是一个负数  再取相反数即可

http://blog.csdn.net/qq172108805/article/details/7855113
有讲解
*/
#include<iostream>
#include<cmath>
using namespace std;
int m,n;
struct node//表示人或房子位置
{
	int x,y;
};
struct edge//用邻接表存边
{
	int v,f,next;
}e[100000];
node *man,*house;//人和房子的数组
int *match,*lx,*ly,*slack,*visx,*visy,*head,nman;//匹配 x的顶标 y的顶标 优化数组 x访问标志 y访问标志 邻接表头节点 人的数量
char map[110][110];
int hungray(int i)//匈牙利算法
{
	int j,v;
	visx[i]=1;
	for(j=head[i];j!=-1;j=e[j].next)
	{
		v=e[j].v;
		if(visy[v]) continue;
		if(lx[i]+ly[v]==e[j].f)
		{
			visy[v]=1;
			if(match[v]==-1||hungray(match[v]))
			{
				match[v]=i;
				return 1;
			}
		}else if(slack[v]>lx[i]+ly[v]-e[j].f)
			slack[v]=lx[i]+ly[v]-e[j].f;
	}
	return 0;
}
int km()//km
{
	memset(match,-1,sizeof(int)*nman);
	int i,j,d;
	memset(ly,0,sizeof(int)*nman);
	for(i=0;i<nman;++i)
	{
		for(j=0;j<nman;++j)
			slack[j]=0x7fffffff;
		while(1)
		{
			memset(visx,0,sizeof(int)*nman);
			memset(visy,0,sizeof(int)*nman);
			if(hungray(i))
				break;
			d=0x7fffffff;
			for(j=0;j<nman;++j)
				if(!visy[j]&&slack[j]<d) d=slack[j];
			for(j=0;j<nman;++j)
			{
				if(visx[j]) lx[j]-=d;
				if(visy[j]) ly[j]+=d;
				else slack[j]-=d;
			}
		}
	}
	d=0;
	for(i=0;i<nman;++i)
		d+=lx[i],d+=ly[i];
	return -d;//取相反数
}
int main()
{
	int i,j,yong;
	while(cin>>n>>m,m+n)
	{
		nman=0;//初始化
		yong=0;
		for(i=0;i<n;++i)//读数据
			for(j=0;j<m;++j)
			{
				cin>>map[i][j];
				if(map[i][j]=='m')
					nman++;
			}

		match=new int[nman];//申请空间
		man=new node[nman];//
		house=new node[nman];//
		lx=new int[nman];//
		ly=new int[nman];//
		slack=new int[nman];//
		visx=new int[nman];//
		visy=new int[nman];//
		head=new int[nman];//
		int jman=0,jhouse=0;

		for(i=0;i<n;++i)//统计人和房子的位置
			for(j=0;j<m;++j)
			{
				if(map[i][j]=='m')
				{
					man[jman].x=i;
					man[jman++].y=j;
				}else if(map[i][j]=='H')
				{
					house[jhouse].x=i;
					house[jhouse++].y=j;
				}
			}

		memset(head,-1,sizeof(int)*nman);//计算人和房子的距离 建边
		for(i=0;i<nman;++i)
		{
			int max=-(0x7fffffff);//在计算的同时给lx数组赋值,就是和i相连的边的最大权
			for(j=0;j<nman;++j)
			{
				e[yong].v=j;
				e[yong].f=-(abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y));//因为求最小权匹配 所以用权的相反数
				if(max<e[yong].f) max=e[yong].f;//更新max
				e[yong].next=head[i];
				head[i]=yong++;
			}
			lx[i]=max;//给lx赋值
		}

		cout<<km()<<endl;//计算 输出

		delete[] match;//释放空间
		delete[] man;
		delete[] house;
		delete[] lx;
		delete[] ly;
		delete[] slack;
		delete[] visx;
		delete[] visy;
		delete[] head;
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值