大致题意:
给定一个N*M的地图,地图上有若干个man和house,且man与house的数量一致。man每移动一格需花费$1(即单位费用=单位距离),一间house只能入住一个man。现在要求所有的man都入住house,求最小费用。
解题思路:
费用流问题。
构图:
把man作为一个顶点集合U,house作为另一个顶点集合V,把U中所有点到V中所有点连线,费用cost[u][v]为abs(△x)+abs(△y),反向弧费用cost[v][u]= -cost[u][v],容量cap[u][v]=1,构成一个多源多汇的二分图。
由于每一个多源多汇的网络流都必有一个与之对应的单源单汇的网络流,为了便于解题,由此构造一个超级源s和超级汇t,超级源s与U中所有点相连,费用cost[s][u]=0(这是显然的),容量cap[s][u]=1;V中所有点与超级汇t相连,费用cost[v][t]=0(这是显然的),容量cap[t][v]=1。
至于其他不连通的点,费用与容量均为0。容量为0的边,可以理解为饱和边,不再连通。而上述的所有边之所以容量初始化为1,是因为每间house只允许入住1个man。而与超级源(汇)相连的边的费用之所以为0,是为了现在所构造的单源单汇网络流最终所求的最小费用等于原来的多源多汇网络流的最小费用。
求解:
接下来的解题方法有关“最小费用最大流”,请未知的同学先去看看相关文献。
其实题目所求的最小费用,就是最短距离。用spfa算法求上述二分图G的最短路径,该最短路径就是图G的所有增广链中费用最小的一条。比较该增广链上所有边的容量,最小的容量就是“可分配的最大流MaxFlow”。
再者就是利用MaxFlow对增广链上的各条边的容量进行调整,正向弧容量减去MaxFlow,反向弧容量加上MaxFlow。然后该条增广链上各条边的费用分别乘以MaxFlow之和,就是第一个man到达合适的house所花费的最小费用。而图G经过调整,变成图G1。
针对图G1再次使用spfa算法,找到第二条增广链.....重复上述算法,直到无法找到增广链为止,则得到的费用和就是所求。
此种解题方法是先计算最小费用,然后再在保证费用最小的情况下,增大流量,最终达到求解目的。理论上,对于有n个man的图,可以找到n条增广链,即重复迭代spfa算法n次。若超过n次,则说明该图存在负权环,无解。但本题并无输出“无解”的要求,故可认为测试数据中不存在此类数据,因此在循环spfa算法时,不必计算次数,用while()足矣。
最后要注意的是:
Discuss上很多人说用了栈空间定义的数组,提交会RE或TLE,数组开大之后才能AC,因此便怪罪于测试数据有误。
其实不然。真正的原因是,题目所提及的N与M纯粹是地图的行数和列数,与man或house的个数无关,而我看了他们的代码,有部分同学却误以为N就是man的个数,导致出现RE。
因此在此强调一下,题目的N不等于man的个数,建议大家先对地图的man个数进行数数,得到man的个数,然后再开堆空间(new函数);而非要开栈空间的同学也未尝不可,由于N与M的值均<=100,且man与house的个数一致,则认为man的个数上限为100*100/2=5000。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
const int maxn=250;
const int maxm=62272;
const int inf=0x4ffffff;
struct Edge
{
int v,next,c,w;
}edge[maxm];
int head[maxn],cnt;
int n,m;
void addedge(int u,int v,int w,int c)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].c=c;
edge[cnt].next=head[u];
head[u]=cnt++;
edge[cnt].v=u;
edge[cnt].w=0;
edge[cnt].c=-c;
edge[cnt].next=head[v];
head[v]=cnt++;
}
int dis[maxn],pre[maxn];
int alpha[maxn];
int que[maxn],qhead,qrear;
int spfa(int s,int e)
{
for(int i=0;i<maxn;++i)
dis[i]=inf;
memset(alpha,0,sizeof(alpha));
dis[s]=0;
alpha[s]=1;
qrear=1;
que[qhead=0]=s;
while(qhead!=qrear)
{
int k=que[qhead++];
qhead%=maxn;
alpha[k]=0;
for(int q=head[k];~q;q=edge[q].next)
if(edge[q].w&&dis[k]+edge[q].c<dis[edge[q].v])
{
dis[edge[q].v]=dis[k]+edge[q].c;
pre[edge[q].v]=q;
if(!alpha[edge[q].v])
{
alpha[edge[q].v]=true;
if(edge[q].c<0)
{
qhead=(qhead-1+maxn)%maxn;
que[qhead]=edge[q].v;
}
else
{
que[qrear++]=edge[q].v;
qrear%=maxn;
}
}
}
}
if(dis[e]==inf)return -1;
int k;
for(int i=e;i!=s;i=edge[pre[i]^1].v)
k=min(k,edge[pre[i]].w);
return k;
}
int mincostmaxflow(int s,int t)
{
int ans=0;
int k;
while(~(k=spfa(s,t)))
{
for(int i=t;i!=s;i=edge[pre[i]^1].v)
{
edge[pre[i]].w-=k;
edge[pre[i]^1].w+=k;
}
ans+=k*dis[t];
}
return ans;
}
struct house
{
int x,y;
}H[1005];
struct man
{
int xx,yy;
}M[1004];
char ma[450][450];
int main()
{
while(cin>>n>>m && n+m)
{
memset(head,-1,sizeof(head));
cnt=0;
int r=1;int t=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>ma[i][j];
if(ma[i][j]=='H')
{
H[r].x=i;
H[r++].y=j;
}
if(ma[i][j]=='m')
{
M[t].xx=i;
M[t++].yy=j;
}
}
}
r--;t--;
for(int i=1;i<=r;i++)
{
addedge(0,i,1,0);
addedge(i+r,r+t+1,1,0);
for(int j=1;j<=t;j++)
{
int dis=abs(H[i].x*1.0-M[j].xx)+abs(H[i].y*1.0-M[j].yy);
addedge(i,j+r,1,dis);
addedge(j+r,i,1,dis);
}
}
// for(int i=1;i<=r;i++)
// {
// addedge(0,i,1,0);
// }
// for(int j=1;j<=t;j++)
// {
// addedge(j+r,r+t+1,1,0);
// }
int ans=mincostmaxflow(0,r+t+1);
cout<<ans<<endl;
}
return 0;
}
大致题意:
给定一个N*M的地图,地图上有若干个man和house,且man与house的数量一致。man每移动一格需花费$1(即单位费用=单位距离),一间house只能入住一个man。现在要求所有的man都入住house,求最小费用。
解题思路:
费用流问题。
构图:
把man作为一个顶点集合U,house作为另一个顶点集合V,把U中所有点到V中所有点连线,费用cost[u][v]为abs(△x)+abs(△y),反向弧费用cost[v][u]= -cost[u][v],容量cap[u][v]=1,构成一个多源多汇的二分图。
由于每一个多源多汇的网络流都必有一个与之对应的单源单汇的网络流,为了便于解题,由此构造一个超级源s和超级汇t,超级源s与U中所有点相连,费用cost[s][u]=0(这是显然的),容量cap[s][u]=1;V中所有点与超级汇t相连,费用cost[v][t]=0(这是显然的),容量cap[t][v]=1。
至于其他不连通的点,费用与容量均为0。容量为0的边,可以理解为饱和边,不再连通。而上述的所有边之所以容量初始化为1,是因为每间house只允许入住1个man。而与超级源(汇)相连的边的费用之所以为0,是为了现在所构造的单源单汇网络流最终所求的最小费用等于原来的多源多汇网络流的最小费用。
求解:
接下来的解题方法有关“最小费用最大流”,请未知的同学先去看看相关文献。
其实题目所求的最小费用,就是最短距离。用spfa算法求上述二分图G的最短路径,该最短路径就是图G的所有增广链中费用最小的一条。比较该增广链上所有边的容量,最小的容量就是“可分配的最大流MaxFlow”。
再者就是利用MaxFlow对增广链上的各条边的容量进行调整,正向弧容量减去MaxFlow,反向弧容量加上MaxFlow。然后该条增广链上各条边的费用分别乘以MaxFlow之和,就是第一个man到达合适的house所花费的最小费用。而图G经过调整,变成图G1。
针对图G1再次使用spfa算法,找到第二条增广链.....重复上述算法,直到无法找到增广链为止,则得到的费用和就是所求。
此种解题方法是先计算最小费用,然后再在保证费用最小的情况下,增大流量,最终达到求解目的。理论上,对于有n个man的图,可以找到n条增广链,即重复迭代spfa算法n次。若超过n次,则说明该图存在负权环,无解。但本题并无输出“无解”的要求,故可认为测试数据中不存在此类数据,因此在循环spfa算法时,不必计算次数,用while()足矣。
最后要注意的是:
Discuss上很多人说用了栈空间定义的数组,提交会RE或TLE,数组开大之后才能AC,因此便怪罪于测试数据有误。
其实不然。真正的原因是,题目所提及的N与M纯粹是地图的行数和列数,与man或house的个数无关,而我看了他们的代码,有部分同学却误以为N就是man的个数,导致出现RE。
因此在此强调一下,题目的N不等于man的个数,建议大家先对地图的man个数进行数数,得到man的个数,然后再开堆空间(new函数);而非要开栈空间的同学也未尝不可,由于N与M的值均<=100,且man与house的个数一致,则认为man的个数上限为100*100/2=5000。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
const int maxn=250;
const int maxm=62272;
const int inf=0x4ffffff;
struct Edge
{
int v,next,c,w;
}edge[maxm];
int head[maxn],cnt;
int n,m;
void addedge(int u,int v,int w,int c)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].c=c;
edge[cnt].next=head[u];
head[u]=cnt++;
edge[cnt].v=u;
edge[cnt].w=0;
edge[cnt].c=-c;
edge[cnt].next=head[v];
head[v]=cnt++;
}
int dis[maxn],pre[maxn];
int alpha[maxn];
int que[maxn],qhead,qrear;
int spfa(int s,int e)
{
for(int i=0;i<maxn;++i)
dis[i]=inf;
memset(alpha,0,sizeof(alpha));
dis[s]=0;
alpha[s]=1;
qrear=1;
que[qhead=0]=s;
while(qhead!=qrear)
{
int k=que[qhead++];
qhead%=maxn;
alpha[k]=0;
for(int q=head[k];~q;q=edge[q].next)
if(edge[q].w&&dis[k]+edge[q].c<dis[edge[q].v])
{
dis[edge[q].v]=dis[k]+edge[q].c;
pre[edge[q].v]=q;
if(!alpha[edge[q].v])
{
alpha[edge[q].v]=true;
if(edge[q].c<0)
{
qhead=(qhead-1+maxn)%maxn;
que[qhead]=edge[q].v;
}
else
{
que[qrear++]=edge[q].v;
qrear%=maxn;
}
}
}
}
if(dis[e]==inf)return -1;
int k;
for(int i=e;i!=s;i=edge[pre[i]^1].v)
k=min(k,edge[pre[i]].w);
return k;
}
int mincostmaxflow(int s,int t)
{
int ans=0;
int k;
while(~(k=spfa(s,t)))
{
for(int i=t;i!=s;i=edge[pre[i]^1].v)
{
edge[pre[i]].w-=k;
edge[pre[i]^1].w+=k;
}
ans+=k*dis[t];
}
return ans;
}
struct house
{
int x,y;
}H[1005];
struct man
{
int xx,yy;
}M[1004];
char ma[450][450];
int main()
{
while(cin>>n>>m && n+m)
{
memset(head,-1,sizeof(head));
cnt=0;
int r=1;int t=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>ma[i][j];
if(ma[i][j]=='H')
{
H[r].x=i;
H[r++].y=j;
}
if(ma[i][j]=='m')
{
M[t].xx=i;
M[t++].yy=j;
}
}
}
r--;t--;
for(int i=1;i<=r;i++)
{
addedge(0,i,1,0);
addedge(i+r,r+t+1,1,0);
for(int j=1;j<=t;j++)
{
int dis=abs(H[i].x*1.0-M[j].xx)+abs(H[i].y*1.0-M[j].yy);
addedge(i,j+r,1,dis);
addedge(j+r,i,1,dis);
}
}
// for(int i=1;i<=r;i++)
// {
// addedge(0,i,1,0);
// }
// for(int j=1;j<=t;j++)
// {
// addedge(j+r,r+t+1,1,0);
// }
int ans=mincostmaxflow(0,r+t+1);
cout<<ans<<endl;
}
return 0;
}