1. 程式人生 > >帶權二分圖匹配KM演算法

帶權二分圖匹配KM演算法

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int INF=1e9;
int flag;
struct zp
{
    int x,y;
}man[200],house[200];
int x[200][200],mn,ho,lx[200],ly[200];
void build_map()//建圖,單向圖
{
    for(int i=0; i<mn; i++)
    {
        for(int j=0
; j<ho; j++) { x[i+1][j+1]=abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y); } } } int visx[200],visy[200],dis[200],ans; int dfspath(int k)//匈牙利演算法,尋找路徑 { visx[k]=1; for(int i=1; i<=mn; i++) { if(!visy[i]&&lx[k]+ly[i]==x[k][i]) { visy[i]=1
; if(dis[i]==-1||dfspath(dis[i])) { dis[i]=k; return 1; } } } return 0; } int KM() { if(!flag)//最小權值處理 { for(int i=1;i<=mn;i++) for(int j=1;j<=ho;j++) x[i][j]=-x[i][j]; } for
(int i=1;i<=mn;i++)//初始化x點集標號 { lx[i]=x[i][1]; for(int j=2;j<=ho;j++) lx[i]=max(lx[i],x[i][j]); } memset(ly,0,sizeof(ly));//初始化y點集標號 memset(dis,-1,sizeof(dis));//儲存對應關係 for(int i=1; i<=mn; i++) { while(1) { memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(dfspath(i))//找到路徑 break; int Min=INF+1; for(int j=1;j<=mn;j++)//擴大子圖範圍 { if(visx[j]) for(int k=1;k<=ho;k++) if(!visy[k]) Min=min(Min,lx[j]+ly[k]-x[j][k]); } for(int j=1;j<=mn;j++)//更新x點集標號 if(visx[j]) lx[j]-=Min; for(int j=1;j<=ho;j++)//更新y點集標號 if(visy[j]) ly[j]+=Min; } } ans=0; for(int i=1;i<=ho;i++)//計算權值和1 ans+=x[dis[i]][i]; if(!flag)//求最小權值 ans=-ans; return ans;//返回權值 } int main() { int n,m; while(~scanf("%d%d",&n,&m)&&(n||m)) { mn=ho=0; char a[200]; memset(x,0,sizeof(x)); //flag=1;//最大權匹配 flag=0;//最小權匹配 ,把權值取反然後求最大權匹配然後把結果取反 for(int i=0; i<n; i++) { scanf("%s",a); for(int j=0; j<m; j++) { if(a[j]=='m') man[mn].x=i+1,man[mn++].y=j+1; else if(a[j]=='H') house[ho].x=i+1,house[ho++].y=j+1; } } build_map(); printf("%d\n",KM()); } }

上面題目連結
題目大概意思是讓求所有的m到H走的所有路之和最小,每一個H只能承載1個m。
KM的一點優化,有兩種方式
hdu 2255題目
一般解法:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int INF=1e9;
int flag;
int x[500][500],n,lx[500],ly[500];
int visx[500],visy[500],dis[500],ans;
int dfspath(int k)//匈牙利演算法,尋找路徑
{
    visx[k]=1;
    for(int i=0; i<n; i++)
    {
        if(!visy[i]&&lx[k]+ly[i]==x[k][i])
        {
            visy[i]=1;
            if(dis[i]==-1||dfspath(dis[i]))
            {
                dis[i]=k;
                return 1;
            }
        }
    }
    return 0;
}
int KM()
{
//    if(!flag)//最小權值處理
//    {
//        for(int i=0;i<=n;i++)
//            for(int j=0;j<n;j++)
//            x[i][j]=-x[i][j];
//    }
    memset(lx,0,sizeof(lx));
    memset(ly,0,sizeof(ly));//初始化y點集標號
    memset(dis,-1,sizeof(dis));//儲存對應關係
    for(int i=0; i<n; i++) //初始化x點集標號
        for(int j=0; j<n; j++)
            lx[i]=max(lx[i],x[i][j]);
    for(int i=0; i<n; i++)
    {
        while(1)
        {
            memset(visx,0,sizeof(visx));
            memset(visy,0,sizeof(visy));
            if(dfspath(i))//找到路徑
                break;
            int Min=INF+1;
            for(int j=0; j<n; j++) //擴大子圖範圍
            {
                if(visx[j])
                    for(int k=0; k<n; k++)
                        if(!visy[k])
                            Min=min(Min,lx[j]+ly[k]-x[j][k]);
            }
            for(int j=0; j<n; j++) //更新x,y點集標號
            {
                if(visx[j])
                    lx[j]-=Min;
                if(visy[j])
                    ly[j]+=Min;
            }
        }
    }
    ans=0;
    for(int i=0; i<n; i++) //計算權值和1
        ans+=x[dis[i]][i];
//        if(!flag)//求最小權值
//            ans=-ans;
    return ans;//返回權值
}
int main()
{
    while(~scanf("%d",&n))
    {
        //flag=1;//最大權匹配
        //flag=0;//最小權匹配 ,把權值取反然後求最大權匹配然後把結果取反
        for(int i=0; i<n; i++)
            for(int j=0; j<n; j++)
                scanf("%d",&x[i][j]);
        //build_map();
        printf("%d\n",KM());
    }
}

優化後的:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int INF=1e9;
int flag;
int x[500][500],n,lx[500],ly[500];
int visx[500],visy[500],dis[500],ans,slack[500],Min;
void build_map()//建圖,單向圖
{

}
int dfspath(int k)//匈牙利演算法,尋找路徑
{
    visx[k]=1;
    for(int i=0; i<n; i++)
    {
        if(visy[i])
            continue;
        int t=lx[k]+ly[i]-x[k][i];
        if(t==0)
        {
            visy[i]=1;
            if(dis[i]==-1||dfspath(dis[i]))
            {
                dis[i]=k;
                return 1;
            }
        }
        else
            //slack[i]=min(slack[i],t);//優化1
            Min=min(Min,t);//優化2
    }
    return 0;
}
int KM()
{
//    if(!flag)//最小權值處理
//    {
//        for(int i=0; i<n; i++)
//            for(int j=0; j<n; j++)
//                x[i][j]=-x[i][j];
//    }
    memset(lx,0,sizeof(lx));
    memset(ly,0,sizeof(ly));//初始化y點集標號
    memset(dis,-1,sizeof(dis));//儲存對應關係
    for(int i=0; i<n; i++) //初始化x點集標號
        for(int j=0; j<n; j++)
            lx[i]=max(lx[i],x[i][j]);
    for(int i=0; i<n; i++)
    {
//        for(int j=0;j<=n;j++)//優化1
//            slack[j]=INF;
        while(1)
        {
            memset(visx,0,sizeof(visx));
            memset(visy,0,sizeof(visy));
            Min=INF+1;
            if(dfspath(i))//找到路徑
                break;
//            for(int j=0; j<n; j++)//擴大子圖範圍  優化1 優化2不要這一步
//                if(!visy[j])
//                    Min=min(Min,slack[j]);
            for(int j=0; j<n; j++) //更新x點集標號
            {
                if(visx[j])
                    lx[j]-=Min;
                if(visy[j])
                    ly[j]+=Min;
//                else//有這一步會超時,但是有的人部落格上有這一步,參考下
//                slack[i]-=Min;
            }
        }
    }
    ans=0;
    for(int i=0; i<n; i++) //計算權值和
        ans+=x[dis[i]][i];
//    if(!flag)//求最小權值
//        ans=-ans;
    return ans;//返回權值
}
int main()
{
    while(~scanf("%d",&n))
    {
        flag=1;
        for(int i=0; i<n; i++)
            for(int j=0; j<n; j++)
                scanf("%d",&x[i][j]);
        printf("%d\n",KM());
    }
}