POJ 2195 Going Home 费用流模版题(附KM算法,转)

题意:

给出一个n*m的图,其中m是人,H是房子,每个人移动一步需要一块,问所有人移动到房子里的最少花费。

建图:

建立一个超级源点和超级汇点,S=0,T=2*num+1。

从源点到所有的人建立一条流,容量cap[S][i]=1,费用cost[S][i]=0;

从房子到汇点建立一条流,容量cap[j][T]=1,费用cost[j][T]=0;

建立所有人到所有房子的流,cap[i][j]=1,cost[i][j]为两个坐标的差值。

然后求一次费用流即可。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cmath>
#include <cstring>
#include <queue>
#include <set>
#include <vector>
#include <stack>
#include <map>
#include <iomanip>
#define PI acos(-1.0)
#define Max 5005
#define inf 1<<28
#define LL(x) (x<<1)
#define RR(x) (x<<1|1)
using namespace std;

struct kdq
{
    int x,y;
}human[Max],house[Max];

int n,m;
int S,T;//源点,汇点
int cost[Max/10][Max];//花费
int cap[Max/10][Max];//容量
int dis[Max];
int path[Max];
bool visit[Max];
int q[Max*10];

int spfa()//最短路
{
    int i,j;
    for(i=0;i<=T;i++)
    dis[i]=inf,path[i]=-1,visit[i]=0;
    dis[S]=0;
    visit[S]=1;
    int num=0,cnt=0;
    q[num++]=S;
    while(num>cnt)
    {
        int temp=q[cnt++];
        visit[temp]=0;
        for(i=0;i<=T;i++)
        {
            if(cap[temp][i]&&dis[temp]+cost[temp][i]<dis[i])
            {
                path[i]=temp;
                dis[i]=dis[temp]+cost[temp][i];
                if(!visit[i])
                {
                    q[num++]=i;
                    visit[i]=1;
                }
            }
        }
    }
    return dis[T]!=inf;
}

int minCost=0;
void getMaxFlow()//增广找最大流
{
    int maxFlow=inf;

    while(spfa())
    {
        int pre=T;
        while(path[pre]!=-1)
        {
            maxFlow=min(maxFlow,cap[path[pre]][pre]);
            pre=path[pre];
        }
        pre=T;
        minCost+=dis[T]*maxFlow;//最小费用
        while(path[pre]!=-1)//更新流
        {
            cap[path[pre]][pre]-=maxFlow;
            cap[pre][path[pre]]+=maxFlow;
            //minCost+=cost[path[pre]][pre]*maxFlow;
            pre=path[pre];
        }
    }
    cout<<minCost<<endl;
    return ;
}

int getdis(kdq x,kdq y)//两个坐标之间的费用
{
    return (abs(x.x-y.x)+abs(y.y-x.y));
}
void build_map(int numm,int numh)//建图
{
    int i,j;
    for(i=1;i<=numm;i++)//计算房子和人之间的费用
    for(j=1;j<=numh;j++){
        cost[i][j+numm]=getdis(human[i],house[j]);
        cost[j+numm][i]=-cost[i][j+numm];//负费用用来回流
    }
    for(i=1;i<=numm;i++)//源点到每个人的cap,cost
        cap[S][i]=1,cost[S][i]=0;
    for(i=1;i<=numh;i++)//房子到汇点的cap,cost
        cap[i+numm][T]=1;
    for(i=1;i<=numm;i++)//每个人和房子之间的cap
    for(j=1;j<=numh;j++)
    cap[i][j+numm]=1;
}
int main()
{
    int i,j,k,l;
    char x;
    while(scanf("%d%d",&n,&m),(n+m))
    {
        memset(cap,0,sizeof(cap));
        memset(cost,0,sizeof(cost));

        int numm=0,numh=0;
        for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
        {
            cin>>x;
            if(x=='m'){
                human[++numm].x=i;
                human[numm].y=j;
            }
            if(x=='H'){
                house[++numh].x=i;
                house[numh].y=j;
            }
        }
        S=0;
        minCost=0;
        T=numm+numh+1;//其实numm==numh。。。。
        build_map(numm,numh);
        getMaxFlow();
        }
    return 0;
}


(转)KM算法,同样可以解此题。留下来看看

这是一个典型的最大匹配的题目,题目意思是给出一些房子和一些人,每个人到每个房子都有一个相应的代价,最后要求怎么安排这些人,房子和人一一配对,使最后的代价最小。
方法是KM算法,是一个求最大(最小)匹配的一个很强大的算法。不过这种题目还可以用费用流来做。

下面是某牛的对KM算法讲解
http://hi.baidu.com/anonympine/blog/item/3ee64954fe6f6256574e0021.html

KM算法是通过给每个顶点一个标号(叫做顶标)来把求最大权匹配的问题转化为求完备匹配的问题的。设顶点Xi的顶标为A[i],顶点Yi的顶标为B[i],顶点Xi与Yj之间的边权为w[i,j]。在算法执行过程中的任一时刻,对于任一条边(i,j),A[i]+B[j]>=w[i,j]始终成立。KM算法的正确性基于以下定理:
若由二分图中所有满足A[i]+B[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。
这个定理是显然的。因为对于二分图的任意一个匹配,如果它包含于相等子图,那么它的边权和等于所有顶点的顶标和;如果它有的边不包含于相等子图,那么它的边权和小于所有顶点的顶标和。所以相等子图的完备匹配一定是二分图的最大权匹配。
初始时为了使A[i]+B[j]>=w[i,j]恒成立,令A[i]为所有与顶点Xi关联的边的最大权,B[j]=0。如果当前的相等子图没有完备匹配,就按下面的方法修改顶标以使扩大相等子图,直到相等子图具有完备匹配为止。
我们求当前相等子图的完备匹配失败了,是因为对于某个X顶点,我们找不到一条从它出发的交错路。这时我们获得了一棵交错树,它的叶子结点全部是X顶点。现在我们把交错树中X顶点的顶标全都减小某个值d,Y顶点的顶标全都增加同一个值d,那么我们会发现:

  • 两端都在交错树中的边(i,j),A[i]+B[j]的值没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。
  • 两端都不在交错树中的边(i,j),A[i]和B[j]都没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。
  • X端不在交错树中,Y端在交错树中的边(i,j),它的A[i]+B[j]的值有所增大。它原来不属于相等子图,现在仍不属于相等子图。
  • X端在交错树中,Y端不在交错树中的边(i,j),它的A[i]+B[j]的值有所减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。
  现在的问题就是求d值了。为了使A[i]+B[j]>=w[i,j]始终成立,且至少有一条边进入相等子图,d应该等于min{A[i]+B[j]-w[i,j]|Xi在交错树中,Yi不在交错树中}。

  以上就是KM算法的基本思路。但是朴素的实现方法,时间复杂度为O(n4)——需要找O(n)次增广路,每次增广最多需要修改O(n)次顶标,每次修改顶标时由于要枚举边来求d值,复杂度为O(n2)。实际上KM算法的复杂度是可以做到O(n3) 的。我们给每个Y顶点一个“松弛量”函数slack,每次开始找增广路时初始化为无穷大。在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slack[j]变成原值与A[i]+B[j]-w[i,j]的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修改顶标后,要把所有的slack值都减去d。

不是特别理解这个算法,但是代码是照着大牛的写出来了.以后再研究吧!!!


#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cmath>
#include <cstring>
#include <queue>
#include <set>
#include <vector>
#include <stack>
#include <map>
#include <iomanip>
#define PI acos(-1.0)
#define Max 2005
#define inf 1<<28
#define LL(x) (x<<1)
#define RR(x) (x<<1|1)
using namespace std;

int n,m;
struct kdq
{
    int x,y;
    void k(int xx,int yy)
    {
        x=xx,y=yy;
    }
    int  diss(const  kdq &aa)
    {
        return abs(x-aa.x)+abs(y-aa.y);
    }
};
kdq a[Max],b[Max];

int x[Max],y[Max];
int match[Max];
int dis[Max][Max];
int km1[Max],km2[Max];
int _man,_house;

int d;
int dfs(int cur)
{
    x[cur]=1;
    for(int i=0; i<_man; i++)
    {
        if(!y[i])
        {
            int t=km1[cur]+km2[i]-dis[cur][i];
            t=-t;
            if(!t)
            {
                y[i]=1;
                if(match[i]==-1||dfs(match[i]))
                {
                    match[i]=cur;
                    return 1;
                }
            }
            else if(d>t)
                d=t;
        }
    }
    return 0;
}

void km()
{
    for(int i=0; i<_man; i++)
    {
        while(1)
        {
            memset(x,0,sizeof(x));
            memset(y,0,sizeof(y));
            d=inf;
            if(dfs(i))
                break;
            for(int j=0; j<_man; j++)
            {
                if(x[j])km1[j]+=d;//最大权做相反处理
                if(y[j])km2[j]-=d;
            }
        }
    }
}

int main()
{
    int i,j,k,l;
    while(scanf("%d%d",&n,&m),(n+m))
    {
        char xx;
        _man=_house=0;
        memset(match,-1,sizeof(match));
        memset(km2,0,sizeof(km2));
        for(i=0; i<=n; i++)
            km1[i]=inf;//最小权,最大权则赋值成0;
        for(i=1; i<=n; i++)
        {
            for(j=1; j<=m; j++)
            {
                cin>>xx;
                if(xx=='m')
                {
                    a[_man++].k(i,j);
                }
                if(xx=='H')
                {
                    b[_house++].k(i,j);
                }
            }
        }
        for(i=0; i<_man; i++)
        {
            for(j=0; j<_house; j++)
            {
                dis[i][j]=a[i].diss(b[j]);
                if(km1[i]>dis[i][j])
                    km1[i]=dis[i][j];
            }
        }
        km();
        int ans=0;
        for(i=0; i<_man; i++)
            ans+=(km1[i]+km2[i]);
        cout<<ans<<endl;
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值