/*
题意:给定一个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;
}