poj2195Going Home(费用流或KM算法)

19 篇文章 0 订阅
9 篇文章 0 订阅

这道题目是比较简单的模板题,我用了两种方法写了一下,需要注意的是用KM算法写的时候,因为这个算法本身求得是二分图最大权匹配,而这道题求的是最小,那么应该在加边的时候把权值乘负一

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<queue>
#include<stack>
using namespace std;
typedef long long ll;
const int maxn=105;
const int INF=0x3f3f3f3f;
int nx,ny;
int g[maxn][maxn];
int linker[maxn],lx[maxn],ly[maxn];
int slack[maxn];
bool visx[maxn],visy[maxn];
bool dfs(int x)
{
    visx[x]=true;
    for(int y=0;y<ny;y++)
    {
        if(visy[y])
            continue;
        int tmp=lx[x]+ly[y]-g[x][y];
        if(tmp==0)
        {
            visy[y]=true;
            if(linker[y]==-1||dfs(linker[y]))
            {
                linker[y]=x;
                return true;
            }
        }
        else if(slack[y]>tmp)
            slack[y]=tmp;
    }
    return false;
}
int KM()
{
    memset(linker,-1,sizeof(linker));
    memset(ly,0,sizeof(ly));
    for(int i=0;i<nx;i++)
    {
        lx[i]=-INF;
        for(int j=0;j<ny;j++)
           {
               if(g[i][j]>lx[i])
            lx[i]=g[i][j];
           }
    }
    for(int x=0;x<nx;x++)
    {
        for(int i=0;i<ny;i++)
            slack[i]=INF;
        while(true)
        {
            memset(visx,0,sizeof(visx));
            memset(visy,0,sizeof(visy));
            if(dfs(x))
                break;
            int d=INF;
            for(int i=0;i<ny;i++)
                if(!visy[i]&&d>slack[i])
                d=slack[i];
            for(int i=0;i<nx;i++)
                if(visx[i])
                lx[i]-=d;
            for(int i=0;i<ny;i++)
            {
                if(visy[i])
                    ly[i]+=d;
                else slack[i]-=d;
            }
        }
    }
    int res=0;
    for(int i=0;i<ny;i++)
        if(linker[i]!=-1)
        res+=g[linker[i]][i];
    return res;
}
char s[105][105];
struct sa
{
    int x,y;
}h[maxn],r[maxn];
int main()
{
    int n,m;
    while(cin>>n>>m)
    {
        if(n==0&&m==0)
            break;
            memset(s,0,sizeof(s));
            memset(g,0,sizeof(g));
            int num1=0,num2=0;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                cin>>s[i][j];
                if(s[i][j]=='H')
                {
                    h[num1].x=i;
                    h[num1++].y=j;
                }
                else if(s[i][j]=='m')
                {
                    r[num2].x=i;
                    r[num2++].y=j;
                }
            }
        }
        for(int i=0;i<num1;i++)
            for(int j=0;j<num2;j++)
        {
            g[i][j]=(abs(h[i].x-r[j].x)+abs(h[i].y-r[j].y))*-1;
          //  cout<<g[i][j]<<endl;
        }
        nx=ny=num1;
      //  cout<<nx<<endl;
        cout<<KM()*-1<<endl;
       /* prepare(num1*2+2,0,2*num1+1);
        for(int i=1;i<=num1;i++)

        {
            addedge(0,i,1,0);
            addedge(i+num1,2*num1+1,1,0);
        }
        for(int i=1;i<=num1;i++)
        {
            for(int j=1;j<=num2;j++)
            {
                int sum=abs(h[i].x-r[j].x)+abs(h[i].y-r[j].y);
                addedge(i,j+num1,1,sum);

            }
        }
        cout<<spfaflow()<<endl;*/

    }
    return 0;
}

费用流:

#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxm=66666;//边的最大数量,为原图的两倍
const int maxn=5555;
const int oo=1e9;
int src,dest,node,edge;
int ver[maxm],cost[maxm],flow[maxm],next[maxm];//node节点数,src源点,dest汇点,edge边数
int head[maxn],dis[maxn],p[maxn],q[maxn];
//head链表头,p记录可行流上节点对应的反向边,dis计算距离  ,q在模拟队列
//int h[55][55];
bool vis[maxn]= {0};
void prepare(int _node,int _src,int _dest)
{
    node=_node,src=_src,dest=_dest;
    for(int i=0; i<node; ++i)
        head[i]=-1;
    edge=0;
}
void addedge(int u,int v,int f,int c)//ver表示边的指向,flow是边的容量,cost是边的费用,next是链表的下一条边
{
    ver[edge]=v,flow[edge]=f,cost[edge]=c,next[edge]=head[u],head[u]=edge++;
    ver[edge]=u,flow[edge]=0,cost[edge]=-c,next[edge]=head[v],head[v]=edge++;

}
bool spfa()
{
    int i,u,v,l,r=0,tmp;
    for(i=0; i<node; ++i)
        dis[i]=oo;
    dis[q[r++]=src]=0;
    p[src]=p[dest]=-1;
    for(l=0; l!=r; (++l==maxn)?l=0:l)
        for(i=head[u=q[l]],vis[u]=0; i>=0; i=next[i])
            if(flow[i]&&dis[v=ver[i]]>(tmp=dis[u]+cost[i]))//u->v容量未饱和,且能够松弛
            {
                dis[v]=tmp;
                p[v]=i^1;
                if(vis[v])
                    continue;
                vis[q[r++]=v]=1;
                if(r==maxn)
                    r=0;
            }
    return p[dest]>-1;
}
int spfaflow()
{
    int i,delta,ret=0;
    while(spfa())
    {
        for(i=p[dest],delta=oo; i>=0; i=p[ver[i]])
            if(flow[i^1]<delta)
                delta=flow[i^1];//可分配最大流 为增广链上的最小容量边的容量
        for(i=p[dest]; i>=0; i=p[ver[i]])
        {
            flow[i]+=delta;//反向弧容量加上可分配最大流
            flow[i^1]-=delta;//正向弧容量减去可分配最大流
        }
        ret+=delta*dis[dest];
    }
    return ret;
}
char s[105][105];
struct sa
{
    int x,y;
}h[maxn],r[maxn];
int main()
{
    int n,m;
    while(cin>>n>>m)
    {
        if(n==0&&m==0)
            break;
            memset(s,0,sizeof(s));
            int num1=0,num2=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                cin>>s[i][j];
                if(s[i][j]=='H')
                {
                    h[++num1].x=i;
                    h[num1].y=j;
                }
                else if(s[i][j]=='m')
                {
                    r[++num2].x=i;
                    r[num2].y=j;
                }
            }
        }
        prepare(num1*2+2,0,2*num1+1);
        for(int i=1;i<=num1;i++)

        {
            addedge(0,i,1,0);
            addedge(i+num1,2*num1+1,1,0);
        }
        for(int i=1;i<=num1;i++)
        {
            for(int j=1;j<=num2;j++)
            {
                int sum=abs(h[i].x-r[j].x)+abs(h[i].y-r[j].y);
                addedge(i,j+num1,1,sum);
            }
        }
        cout<<spfaflow()<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值