带权二分图匹配KM算法

来源:互联网 发布:知乎 正装西服品牌 编辑:程序博客网 时间:2024/05/22 00:23
#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());    }}
0 0