匈牙利算法

来源:互联网 发布:qq飞车指挥官全29数据 编辑:程序博客网 时间:2024/05/20 16:35

参考博客:http://blog.csdn.net/dark_scope/article/details/8880547(博主写的很详细)

在你的手上有N个剩男,M个剩女,每个人都可能对多名异性有好感(惊讶-_-||暂时不考虑特殊的性取向),如果一对男女互有好感,那么你就可以把这一对撮合在一起,现在让我们无视掉所有的单相思(好忧伤的感觉快哭了),你拥有的大概就是下面这样一张关系图,每一条连线都表示互有好感。


本着救人一命,胜造七级浮屠的原则,你想要尽可能地撮合更多的情侣,匈牙利算法的工作模式会教你这样做:

===============================================================================

 先试着给1号男生找妹子,发现第一个和他相连的1号女生还名花无主,got it,连上一条蓝线


===============================================================================

接着给2号男生找妹子,发现第一个和他相连的2号女生名花无主,got it


===============================================================================

接下来是3号男生,很遗憾1号女生已经有主了,怎么办呢?

我们试着给之前1号女生匹配的男生(也就是1号男生)另外分配一个妹子。

(黄色表示这条边被临时拆掉)

与1号男生相连的第二个女生是2号女生,但是2号女生也有主了,怎么办呢?我们再试着给2号女生的原配(发火发火)重新找个妹子(注意这个步骤和上面是一样的,这是一个递归的过程)


此时发现2号男生还能找到3号女生,那么之前的问题迎刃而解了,回溯回去

2号男生可以找3号妹子~~~                  1号男生可以找2号妹子了~~~                3号男生可以找1号妹子

所以第三步最后的结果就是:


===============================================================================

 接下来是4号男生,很遗憾,按照第三步的节奏我们没法给4号男生出来一个妹子,我们实在是无能为力了……香吉士同学走好。

(以上为借鉴)。代码如下
int match[maxn],n;//match表示谁与谁匹配
bool used[maxn],link[maxn][maxn];//used表示是否尝试给变过他的属性,及在find中是否扫过他,如果已经扫过了说明他已经用了。link表示关系。***used每次都要更新
int  find(int a){
    for(int i=0;i<n;i++){
        if(link[a][i]&&!used[i])
        {
            used[i]=1;
            if(match[i]==-1||find(match[i]))
            {
                match[i]=a;
                return 1;
            }
        }
    }
    return 0;
}
int hungary(){
    int ans=0;
    memset(match,-1,sizeof(match));
    for(int i=0;i<n;i++)
    {
        memset(used,0,sizeof(used));********
        ans+=find(i);
    }
    return ans;

}

例题:hdu 1068

题意   有一群人,有些人彼此之间有好感,现在老师要挑出一群人,并且彼此之间没有好感

就是求最大独立集

由于是一般图,所以要把每一个点分成两个p1与p2,p1表示从 p出去的边,p2表示进入p的边,若此可以当作二分,所以求出的最大匹配是真是值得二倍

所以结果就是n-ans/2;

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>


using namespace std;
const int maxn=1000;
int match[maxn],n;
bool used[maxn],link[maxn][maxn];
int  find(int a){
    for(int i=0;i<n;i++){
        if(link[a][i]&&!used[i])
        {
            used[i]=1;
            if(match[i]==-1||find(match[i]))
            {
                match[i]=a;
                return 1;
            }
        }
    }
    return 0;
}
int hungary(){
    int ans=0;
    memset(match,-1,sizeof(match));
    for(int i=0;i<n;i++)
    {
        memset(used,0,sizeof(used));
        ans+=find(i);
    }
    return ans;
}
int main()
{
    int num,a,b;
    while(~scanf("%d",&n)){
        memset(link,0,sizeof(link));
        for(int i=0;i<n;i++){
            scanf("%d: (%d)",&a,&num);
            for(int j=0;j<num;j++)
            {
                scanf("%d",&b);
                link[a][b]=1;
            }
        }
        int ans=hungary();
        printf("%d\n",n-ans/2);
    }
    return 0;
}

hdu 4185

题意:给你一个图每件物品会占用连着的两个#问你可以放多少个物品

题解:通过把#抽象出来就是给#一个编号如果有连着的两个#就把他们连起来,求最大匹配就可以了,因为是无向图,边都加了两次,所以得出的最大匹配要除以2.

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>


using namespace std;
const int N=605;
const int M=6005;
int len,pos[N][N];
int dir[4][2]={0,1,0,-1,-1,0,1,0},link[N];
bool used[N];
char str[N][N];
vector< int >edge[M];
int find(int k){
    for(int i=0;i<edge[k].size();i++){
        int v=edge[k][i];
        if(!used[v]){
            used[v]=1;
            if(link[v]==-1||find(link[v])){
                link[v]=k;
                return 1;
            }
        }
    }
    return 0;
}
int hungry(int n){
    int ans=0;
    memset(link,-1,sizeof(link));
    for(int i=0;i<n;i++){
        memset(used,0,sizeof(used));
        ans+=find(i);
    }
    return ans;
}
int main()
{
    int n,t,T;
    t=1;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        len=0;
        for(int i=0;i<n;i++){
            scanf("%s",str[i]);
            for(int j=0;j<n;j++){
                if(str[i][j]=='#')
                {
                    pos[i][j]=len++;
                }
            }
        }
        for(int i=0;i<M;i++)
        {
            edge[i].clear();
        }
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                int x,y,u,v;
                if(str[i][j]=='.')continue;
                u=pos[i][j];
                for(int k=0;k<4;k++){
                    x=dir[k][0]+i;
                    y=dir[k][1]+j;
                     if(x>=0&&x<n&&y>=0&&y<n&&str[x][y]=='#'){
                        v=pos[x][y];
                        edge[u].push_back(v);
                        edge[v].push_back(u);
                     }
                }
            }
        }
        int ans=hungry(len);
        printf("Case %d: %d\n",t++,ans/2);
    }
    return 0;
}

0 0