pku2195Going Home 的两种算法KM,最小费用最大流

//题意:一个人匹配一个房子,使人的移动步伐之和最小
//思路:利用KM算法计算二分图最大权匹配AC 0MS,算法一如下:
ContractedBlock.gif ExpandedBlockStart.gif Code
#include <iostream>
#include 
<cmath>
using namespace std;

const int MAXN = 102*102/2;
const int MAX = -1u>>1;
typedef 
struct
{
    
int x,y;
}Point;
Point House[MAXN],Man[MAXN];
int numH,numM;
int map[MAXN][MAXN],N,M;
int match[MAXN],lx[MAXN],ly[MAXN];
int sx[MAXN],sy[MAXN];
char a[MAXN][MAXN];

bool path(int u)//匈牙利算法,返回最佳匹配
{
    sx[u] 
= 1;
    
forint i = 0 ; i < numM ; i ++ )
        
if!sy[i] && lx[u] + ly[i] == map[u][i] )
        {
            sy[i] 
= 1;
            
if( match[i] == -1 || path(match[i]) )
            {
                match[i] 
= u;
                
return true;
            }
        }
    
return false;
}

int KM()
{
    
int i,j,u;
    
for( i = 0 ; i < numH ; i ++ )//一开始让lx最大化,ly为零,满足lx[i]+ly[j]>=weight[i][j]
    {
        lx[i] 
= -MAX;
        ly[i] 
= 0;
        
for( j = 0 ; j < numM ; j ++ )
            
if( lx[i] < map[i][j] )
                lx[i] 
= map[i][j];
    }
    memset(match,
-1,sizeof(match));
    
for( u = 0 ; u < numH ; u ++ )//求n次增广路径
    {
        
while1 )
        {
            memset(sx,
0,sizeof(sx));//为0时未匹配,1时匹配
            memset(sy,0,sizeof(sy));

            
if( path(u) )//寻到增广路,满足lx[i]+ly[j]=weigh[i][j],退出
                break;

            
int dx = MAX;
            
for( i = 0 ; i < numM ; i ++ )//只有i匹配,j未匹配才可能找到一条更短的路
                if( sx[i] )//匹配
                    for( j = 0 ; j < numH ; j ++ )
                        
if!sy[j] )//未匹配
                            dx = __min(lx[i]+ly[j]-map[i][j],dx);

            
for( i = 0 ; i < numM ; i ++ )//不断的缩小lx[i]+ly[j],最终达到lx[i]+ly[j]=weigh[i][j]
            {
                
if( sx[i] )
                    lx[i] 
-= dx;
                
if( sy[i] )
                    ly[i] 
+= dx;
            }
        }
    }
    
int sum = 0;
    
for( i = 0 ; i < numH ; i ++ )
        
if( match[i] != -1 )
            sum 
+= map[match[i]][i];
    
return sum;
}

int main()
{
    
int i,j;
    
//freopen("2195.txt","r",stdin);
    while1 )
    {
        scanf(
"%d %d",&N,&M);
        
if( N == 0 && M == 0 ) break;

        
for( i = 0 ; i < N ; i ++ )
            scanf(
"%s",a[i]);
        
        numH 
= numM = 0
        
for( i = 0 ; i < N ; i ++ )
            
for( j = 0 ; j < M ; j ++ )
            {
                
if( a[i][j] == 'H' )
                {
                    House[numH].x 
= i;
                    House[numH
++].y = j;
                }
                
else if( a[i][j] == 'm' )
                {
                    Man[numM].x 
= i;
                    Man[numM
++].y = j;
                }
            }
        
for( i = 0 ; i < numH ; i ++ )//构图
            for( j = 0 ; j < numM ; j ++ )
                map[i][j] 
= -(abs(House[j].x - Man[i].x) + abs(House[j].y - Man[i].y));

        
int ans = KM();
        printf(
"%d\n",-ans);
    }

    
return 0;
}
//算法二:最小费用最大流,以SPFA实现
代码如下:AC 63MS
ContractedBlock.gif ExpandedBlockStart.gif Code
#include <iostream>
#include 
<queue>
using namespace std;

const int MAXN = 205;
const int INF = -1u>>1;
int mNum,man[MAXN][2];//存储人的位置
int hNum,house[MAXN][2];//存储房子的位置
int cost[MAXN][MAXN];//cost[I][J]表示从第i个人走到第j个房子要花费几步路
int map[MAXN][MAXN];//map[i][j]表示第i个人到第j个房子有通路
int flow[MAXN][MAXN],min_flow[MAXN];
int pre[MAXN],dis[MAXN];
int hasMan[MAXN];//标志某房子里有没有住人
int N,M;

bool SPFA(int num,int s,int e)
{
    queue
<int>Q;
    
int i;
    memset(pre,
-1,sizeof(pre));
    memset(hasMan,
0,sizeof(hasMan));
    
for( i = 0 ; i < num ; i ++ )
        dis[i] 
= INF;
    dis[s] 
= 0;
    Q.push(s);
    hasMan[s] 
= 1;
    min_flow[s] 
= INF;
    
while!Q.empty() )
    {
        
int k = Q.front();
        Q.pop();
        
for( i = 0 ; i < num ; i ++ )
        {
            
if( map[k][i] - flow[k][i] > 0 )
                
if( dis[k] + cost[k][i] < dis[i] )
                {
                    dis[i] 
= dis[k] + cost[k][i];
                    
if!hasMan[i] )//注意题目是即使房子原先已经住人了,当还是可以站在上面
                    {
                        hasMan[i] 
= 1;
                        Q.push(i);
                    }
                    pre[i] 
= k;
                    min_flow[i] 
= __min(min_flow[k],map[k][i] - flow[k][i]);
                }
        }
        hasMan[k] 
= 0;
    }
    
if( pre[e] != -1 )
        
return true;
    
return false;
}

int Min_Cost_Max_Flow(int num,int s,int e)//num为节点数,s为源点,e为汇点
{
    
int ans = 0;
    memset(flow,
0,sizeof(flow));
    
while(SPFA(num,s,e))//寻找增广路
    {
        
int k = e;
        
while( pre[k] >= 0 )
        {
            flow[pre[k]][k] 
+= min_flow[e];
            flow[k][pre[k]] 
= -flow[pre[k]][k];
            k 
= pre[k];
        }
        ans 
+= dis[e];
    }
    
return ans;
}

int main()
{
    
char c[MAXN];
    
int i,j;
    
//freopen("2195.txt","r",stdin);
    while1 )
    {
        scanf(
"%d %d",&N,&M);
        
if( N == 0 && M == 0 ) break;
        getchar();
        mNum 
= hNum = 0;
        
for( i = 1 ; i <= N ; i ++ )
        {
            scanf(
"%s",c);
            
for( j = 1 ; j <= M ; j ++ )
            {
                
if( c[j-1== 'm' )
                {
                    man[mNum][
0= i;
                    man[mNum
++][1= j;
                }
                
else if( c[j-1== 'H' )
                {
                    house[hNum][
0= i;
                    house[hNum
++][1= j;
                }
            }
        }
//printf("%d %d\n",mNum,hNum);
        memset(map,0,sizeof(map));
        memset(cost,
0,sizeof(cost));
        
for( i = 1 ; i <= mNum ; i ++ )
        {
            
for( j = 1 ; j <= hNum ; j ++ )
            {
//建立人与房子的关系 
                cost[i][j+mNum] = abs(man[i-1][0- house[j-1][0]) + abs(man[i-1][1- house[j-1][1]);
                cost[j
+mNum][i] = -cost[i][j+mNum];
                map[i][j
+mNum] = 1;//注意是单向的
                
            }  
        }
        
for( i = 1 ; i <= mNum ; i ++ )
            map[
0][i] = 1;//建立源点,从源点到人联通
        for( j = 1 ; j <= hNum ; j ++ )
            map[j
+mNum][mNum+hNum+1= 1;//建立汇点,房子与汇点联通
        printf("%d\n",Min_Cost_Max_Flow(hNum+mNum+2,0,hNum+mNum+1));
    }
    
return 0;
}

转载于:https://www.cnblogs.com/shenyaoxing/archive/2009/10/11/1581024.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值