POJ 2195 Going Home

最小费用流第二题,貌似比第一题简单啊……

(这道题也可以用二分图的最优匹配来解。下面有KM算法的解法。)

题目大意:

在一个地图上给出房子的位置和人的位置,人和房子的数量是相等的。人要回到房子里,每个房子只能回一个人。人向房子每移动一个单位需要花费$1,求人全部回房子的最小花费。


注意事项 :

因为地图上的人和房子数不一定 ,最大可能是10000个点都用上,所以我就这么试了一下,结果MLE了,后来经尝试不超过1005个点,因为我开的数组大小是1005的。


下面是代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <queue>

using namespace std;

const int inf=1<<30,M=1005;
int n,m;
short c[M][M],f[M][M],w[M][M];
int dis[M],pre[M];
bool vis[M];
char s[105][105];
struct node
{
    int x,y;
} point[M];
int min(int a,int b)
{
    if(a>b)
    {
        a=b;
    }
    return a;
}
bool spfa()
{
    int i,j;
    for(i=0; i<=n+m+1; i++) //初始化
    {
        dis[i]=inf;
        pre[i]=-1;
        vis[i]=false;
    }
    dis[0]=0;
    vis[0]=true;
    queue <int> q;
    q.push(0);
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        vis[t]=false;
        for(i=1; i<=n+m+1; i++)
        {
            if(c[t][i]>f[t][i]&&dis[i]>dis[t]+w[t][i])  //如果流量没有到最大且超级原点经过点t到i的费用比直接到i小
            {
                dis[i]=dis[t]+w[t][i]; //更新到i点的最小花费
                pre[i]=t; //更新最短路径中点i的前驱为t
                if(!vis[i]) //如果点i没在队列中
                {
                    q.push(i);   //将点i放入队列
                    vis[i]=true; //标记已在队列中
                }
            }
        }
    }
    if(pre[n+m+1]==-1)//如果超级汇点没有在对短路中 (因为没有前驱)
        {
            return false;//返回寻找最短路失败
        }
        return true;  //返回最短路寻找成功
}
int main()
{
    int n1,m1;
    while(scanf("%d%d",&n1,&m1),n1||m1)
    {
        int i,j;
        n=1;
        m=0;
        for(i=0; i<n1; i++)
        {
            scanf("%s",s[i]);
            for(j=0; j<m1; j++)
            {
                if(s[i][j]=='m')
                {
                    point[n].x=i;
                    point[n].y=j;
                    n++;
                }
            }
        }
        for(i=0; i<n1; i++)
        {
            for(j=0; j<m1; j++)
            {
                if(s[i][j]=='H')
                {
                    point[n+m].x=i;
                    point[n+m].y=j;
                    m++;
                }
            }
        }
        memset(c,0,sizeof(c));
        memset(f,0,sizeof(f));
        memset(w,0,sizeof(w));
        n--;
        for(i=1; i<=n; i++)
        {
            for(j=1; j<=m; j++)
            {
                w[i][n+j]=abs(point[i].x-point[n+j].x)+abs(point[i].y-point[n+j].y);
                w[n+j][i]=-w[i][n+j];
            }
        }
        for(i=1; i<=n; i++)
        {
            c[0][i]=1;
        }
        for(i=n+1; i<=n+m; i++)
        {
            c[i][n+m+1]=1;
        }
        for(i=1; i<=n; i++)
        {
            for(j=1; j<=m; j++)
            {
                c[i][n+j]=1;
            }
        }
        while(spfa())
        {
            int maxflow=inf;//初始化为最大值
            int p=n+m+1;
            while(pre[p]!=-1) //遍历最小费用增广路
            {
                maxflow=min(maxflow,c[pre[p]][p]-f[pre[p]][p]);//寻找关键流量,及最短路上的最小流量
                p=pre[p];
            }
            p=n+m+1; //再次初始化;
            while(pre[p]!=-1)  //再次遍历最小费用增广路
            {
                f[pre[p]][p]+=maxflow;
                f[p][pre[p]]=-f[pre[p]][p];  //调整流量
                p=pre[p];
            }
        }
        int ans=0,d;
        for(j=1; j<=n; j++)
        {
            for(d=1; d<=m; d++)
            {
                ans+=f[j][d+n]*w[j][d+n];//计算总费用
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

这道题也可以用二分图的最优匹配(KM算法来做),就简单了(2014年2月13号修改)。

上面是KM算法的情况,下面是最小费用最大流的情况。



可见在这个题上,KM算法还是有优势的。



下面是代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
using namespace std;
const int Max=2005;
const int  inf=1<<28;
struct node
{
    int x,y;
    void k(int xx,int yy)
    {
        x=xx,y=yy;
    }
    int  diss(const node &aa)
    {
        return abs(x-aa.x)+abs(y-aa.y);
    }
} a[Max],b[Max];
int d,n,m,cnta,cntb,pre[Max],dis[Max][Max],km1[Max],km2[Max];
bool x[Max],y[Max];
void init()
{
    cnta=0;
    cntb=0;
    memset(pre,-1,sizeof(pre));
    memset(km2,0,sizeof(km2));
    for(int i=0; i<=n; i++)
    {
        km1[i]=inf;
    }
}
bool dfs(int src)
{
    x[src]=true;
    for(int i=0; i<cnta; i++)
    {
        if(!y[i])
        {
            int t = km1[src]+km2[i]-dis[src][i];
            t=-t;
            if(!t)
            {
                y[i]=true;
                if(pre[i]==-1||dfs(pre[i]))
                {
                    pre[i]=src;
                    return true;
                }
            }
            else if(d>t)d=t;
        }
    }
    return false;
}
void km()
{
    for(int i=0; i<cnta; i++)
    {
        while(1)
        {
            memset(x,false,sizeof(x));
            memset(y,false,sizeof(y));
            d=inf;
            if(dfs(i))break;
            for(int j=0; j<cnta; j++)
            {
                if(x[j])km1[j]+=d;
                if(y[j])km2[j]-=d;
            }
        }
    }
}
int main()
{
    while(scanf("%d%d",&n,&m),n||m)
    {
        char s[105];
        init();
        for(int i=1 ; i<=n; i++)
        {
            cin >>s;
            for(int j=1; j<=m; j++ )
            {
                if(s[j-1]=='m')a[cnta++].k(i,j);
                else if(s[j-1]=='H')b[cntb++].k(i,j);
            }
        }
        for(int i=0; i<cnta; i++)
        {
            for(int j=0; j<cntb; 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(int i=0; i<cnta; i++)
        {
            ans+=(km1[i]+km2[i]);
        }
        printf("%d\n",ans);
    }
    return 0;
}


你可能感兴趣的:(poj,km,二分图,最小费用最大流)