1、N*M的矩阵中,有k个人和k个房子,每个人分别进入一个房子中,求所有人移动的最小距离。
2、人看成源点,房子看成汇点,求最小费用最大流。
建图--
人指向房子,容量为1,费用为人到房子的曼哈顿距离。
建立超级源点和超级汇点:超级源点指向人,容量为1,费用为0;房子指向超级汇点,容量为1,费用为0。
求超级源点到超级汇点的最小费用最大流即可。
ps:容量为什么都设为1?---有待研究。。
今天又看了下,设为1是必须的。
超级源点指向人,容量为1,保证人这个点只有一个人;
房子指向超级汇点,容量为1,保证一个房子只能容纳一个人。
然而,人指向房子,容量为1,这并不是必须的,因为前两个条件已经足够了。为了简单,这里仍然把这个容量设为1。
3、
1、Bellman-Ford:
#include<iostream> #include<stdio.h> #include<vector> #include<string.h> #include<queue> #include<algorithm> using namespace std; const int maxn=256; const int INF=0x3f3f3f3f; struct Edge{ int from,to,cap,flow,cost; Edge(int u,int v,int c,int f,int w):from(u),to(v),cap(c),flow(f),cost(w){} }; struct MCMF{ int n,m; vector<Edge>edges; vector<int>G[maxn]; int inq[maxn];//是否在队列中 int d[maxn];//Bellman-Ford int p[maxn];//上一条弧 int a[maxn];//可改进量 void init(int n){ this->n=n; for(int i=0;i<n;i++)G[i].clear(); edges.clear(); } void AddEdge(int from,int to,int cap,int cost){ edges.push_back(Edge(from,to,cap,0,cost)); edges.push_back(Edge(to,from,0,0,-cost)); m=edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool BellmanFord(int s,int t,int &flow,long long &cost){ for(int i=0;i<n;i++)d[i]=INF; memset(inq,0,sizeof(inq)); d[s]=0;inq[s]=1;p[s]=0;a[s]=INF; queue<int>Q; Q.push(s); while(!Q.empty()){ int u=Q.front();Q.pop(); inq[u]=0; for(int i=0;i<(int)G[u].size();i++){ Edge &e=edges[G[u][i]]; if(e.cap>e.flow&&d[e.to]>d[u]+e.cost){ d[e.to]=d[u]+e.cost; p[e.to]=G[u][i]; a[e.to]=min(a[u],e.cap-e.flow); if(!inq[e.to]){Q.push(e.to);inq[e.to]=1;} } } } if(d[t]==INF)return false; flow+=a[t]; cost+=(long long)d[t]*(long long)a[t]; for(int u=t;u!=s;u=edges[p[u]].from){ edges[p[u]].flow+=a[t]; edges[p[u]^1].flow-=a[t]; } return true; } //需要保证初始网络中没有负权圈 int MincostMaxflow(int s,int t,long long &cost){ int flow=0;cost=0; while(BellmanFord(s,t,flow,cost)); return flow; } }MM; struct point{//图中的一个点 int x; int y; }; point man[128];//存储人 point house[128];//存储房子 int man_sum;//人的总数 int house_sum;//房子的总数 int main(){ int N,M;//N*M的矩阵 char str[128];// int S,T;//超级源点,超级汇点 long long mincost;//最小花费 int maxflow;//最大流 while(~scanf("%d%d",&N,&M)){ if(N==0&&M==0)break; man_sum=0; house_sum=0; for(int i=0;i<N;++i){ scanf("%s",str); for(int j=0;j<M;++j){ if(str[j]=='m'){ man[man_sum].x=i; man[man_sum++].y=j; } else if(str[j]=='H'){ house[house_sum].x=i; house[house_sum++].y=j; } } } MM.init(man_sum+house_sum+2); for(int i=0;i<man_sum;++i){ for(int j=0;j<house_sum;++j){ MM.AddEdge(i,man_sum+j,1,abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y)); } } //超级源点 S=man_sum+house_sum; for(int i=0;i<man_sum;++i){ MM.AddEdge(S,i,1,0); } //超级汇点 T=man_sum+house_sum+1; for(int i=0;i<house_sum;++i){ MM.AddEdge(man_sum+i,T,1,0); } maxflow=MM.MincostMaxflow(S,T,mincost); printf("%lld ",mincost); } return 0; }
2、SPFA版费用流:
/* SPFA版费用流 最小费用最大流,求最大费用最大流只需要取相反数,结果取相反数即可。 点的总数为N,点的编号0~N-1 */ #include<iostream> #include<stdio.h> #include<string.h> #include<queue> #include<algorithm> using namespace std; const int MAXN=256; const int MAXM=20500; const int INF=0x3f3f3f3f; struct Edge{ int to,next,cap,flow,cost; }edge[MAXM]; int head[MAXN],tol; int pre[MAXN],dis[MAXN]; bool vis[MAXN]; int N;//节点总个数,节点编号从0~N-1 void init(int n){ N=n; tol=0; memset(head,-1,sizeof(head)); } void addedge(int u,int v,int cap,int cost){ edge[tol].to=v; edge[tol].cap=cap; edge[tol].cost=cost; edge[tol].flow=0; edge[tol].next=head[u]; head[u]=tol++; edge[tol].to=u; edge[tol].cap=0; edge[tol].cost=-cost; edge[tol].flow=0; edge[tol].next=head[v]; head[v]=tol++; } bool spfa(int s,int t){ queue<int>q; for(int i=0;i<N;i++){ dis[i]=INF; vis[i]=false; pre[i]=-1; } dis[s]=0; vis[s]=true; q.push(s); while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=false; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].to; if(edge[i].cap>edge[i].flow&&dis[v]>dis[u]+edge[i].cost){ dis[v]=dis[u]+edge[i].cost; pre[v]=i; if(!vis[v]){ vis[v]=true; q.push(v); } } } } if(pre[t]==-1)return false; else return true; } //返回的是最大流,cost存的是最小费用 int minCostMaxflow(int s,int t,int &cost){ int flow=0; cost=0; while(spfa(s,t)){ int Min=INF; for(int i=pre[t];i!=-1;i=pre[edge[i^1].to]){ if(Min>edge[i].cap-edge[i].flow) Min=edge[i].cap-edge[i].flow; } for(int i=pre[t];i!=-1;i=pre[edge[i^1].to]){ edge[i].flow+=Min; edge[i^1].flow-=Min; cost+=edge[i].cost*Min; } flow+=Min; } return flow; } struct point{//图中的一个点 int x; int y; }; point man[128];//存储人 point house[128];//存储房子 int man_sum;//人的总数 int house_sum;//房子的总数 int main(){ int N,M;//N*M的矩阵 char str[128]; int S,T;//超级源点,超级汇点 int mincost;//最小花费 int maxflow;//最大流 while(~scanf("%d%d",&N,&M)){ if(N==0&&M==0)break; man_sum=0; house_sum=0; for(int i=0;i<N;++i){ scanf("%s",str); for(int j=0;j<M;++j){ if(str[j]=='m'){ man[man_sum].x=i; man[man_sum++].y=j; } else if(str[j]=='H'){ house[house_sum].x=i; house[house_sum++].y=j; } } } init(man_sum+house_sum+2); for(int i=0;i<man_sum;++i){ for(int j=0;j<house_sum;++j){ addedge(i,man_sum+j,1,abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y)); } } //超级源点 S=man_sum+house_sum; for(int i=0;i<man_sum;++i){ addedge(S,i,1,0); } //超级汇点 T=man_sum+house_sum+1; for(int i=0;i<house_sum;++i){ addedge(man_sum+i,T,1,0); } maxflow=minCostMaxflow(S,T,mincost); printf("%d ",mincost); } return 0; }
3、zkw费用流:
/* zkw费用流 对于二分图类型的比较高效 */ #include<iostream> #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; const int MAXN=256; const int MAXM=20500; const int INF=0x3f3f3f3f; struct Edge{ int to,next,cap,flow,cost; Edge(int _to=0,int _next=0,int _cap=0,int _flow=0,int _cost=0): to(_to),next(_next),cap(_cap),flow(_flow),cost(_cost){} }edge[MAXM]; struct ZKW_MinCostMaxFlow{ int head[MAXN],tot; int cur[MAXN]; int dis[MAXN]; bool vis[MAXN]; int ss,tt,N;//源点、汇点和点的总个数(编号是0~N-1),不需要额外赋值,调用会直接赋值 int min_cost,max_flow; void init(){ tot=0; memset(head,-1,sizeof(head)); } void addedge(int u,int v,int cap,int cost){ edge[tot]=Edge(v,head[u],cap,0,cost); head[u]=tot++; edge[tot]=Edge(u,head[v],0,0,-cost); head[v]=tot++; } int aug(int u,int flow){ if(u==tt)return flow; vis[u]=true; for(int i=cur[u];i!=-1;i=edge[i].next){ int v=edge[i].to; if(edge[i].cap>edge[i].flow&&!vis[v]&&dis[u]==dis[v]+edge[i].cost){ int tmp=aug(v,min(flow,edge[i].cap-edge[i].flow)); edge[i].flow+=tmp; edge[i^1].flow-=tmp; cur[u]=i; if(tmp)return tmp; } } return 0; } bool modify_label(){ int d=INF; for(int u=0;u<N;u++) if(vis[u]) for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].to; if(edge[i].cap>edge[i].flow&&!vis[v]) d=min(d,dis[v]+edge[i].cost-dis[u]); } if(d==INF)return false; for(int i=0;i<N;i++) if(vis[i]){ vis[i]=false; dis[i]+=d; } return true; } /* 直接调用获取最小费用和最大流 输入:start-源点,end-汇点,n-点的总个数(编号从0开始) 返回值:pair<int,int>第一个是最小费用,第二个是最大流 */ pair<int,int> mincostmaxflow(int start,int end,int n){ ss=start,tt=end,N=n; min_cost=max_flow=0; for(int i=0;i<n;i++)dis[i]=0; while(1){ for(int i=0;i<n;i++)cur[i]=head[i]; while(1){ for(int i=0;i<n;i++)vis[i]=false; int tmp=aug(ss,INF); if(tmp==0)break; max_flow+=tmp; min_cost+=tmp*dis[ss]; } if(!modify_label())break; } return make_pair(min_cost,max_flow); } }solve; struct point{//图中的一个点 int x; int y; }; point man[128];//存储人 point house[128];//存储房子 int man_sum;//人的总数 int house_sum;//房子的总数 int main(){ int N,M;//N*M的矩阵 char str[128]; int S,T;//超级源点,超级汇点 //int mincost; //int maxflow; pair<int,int>pr;//最小花费,最大流 while(~scanf("%d%d",&N,&M)){ if(N==0&&M==0)break; man_sum=0; house_sum=0; for(int i=0;i<N;++i){ scanf("%s",str); for(int j=0;j<M;++j){ if(str[j]=='m'){ man[man_sum].x=i; man[man_sum++].y=j; } else if(str[j]=='H'){ house[house_sum].x=i; house[house_sum++].y=j; } } } solve.init(); for(int i=0;i<man_sum;++i){ for(int j=0;j<house_sum;++j){ solve.addedge(i,man_sum+j,1,abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y)); } } //超级源点 S=man_sum+house_sum; for(int i=0;i<man_sum;++i){ solve.addedge(S,i,1,0); } //超级汇点 T=man_sum+house_sum+1; for(int i=0;i<house_sum;++i){ solve.addedge(man_sum+i,T,1,0); } pr=solve.mincostmaxflow(S,T,man_sum+house_sum+2); printf("%d ",pr.first); } return 0; }