二分图匹配:匈牙利算法

二分图

  • 点能分成两个独立的点集

è¿éåå¾çæè¿°

  • 匹配点:匹配边上的两点
  • 最大匹配:选出最大的边数,使得这些边的顶点不重复
  • 完美匹配:所有顶点都是匹配点。完美匹配一定时最大匹配,最大匹配不一定是完美匹配
  • 最小覆盖:分为最小顶点覆盖与最小路径覆盖
  • 最小顶点覆盖=最大匹配。选出最少的点集,覆盖所有的边
  • 最小路径覆盖=n-最大匹配。(n为所有顶点)用最少的不相交路径覆盖所有点
  • 最大独立集:n-最大匹配。(n为所有顶点)找出一个点集,使得点集中任意两点在图中无边。最大独立集与最小点覆盖互补。
  • 最大团=补图的最大独立集。选出一些顶点,使得这些点两两有边

匈牙利算法

匈牙利算法是由匈牙利数学家Edmonds于1965年提出,因而得名。匈牙利算法是基于Hall定理中充分性证明的思想,它是部图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最大匹配的算法。

时间复杂度邻接矩阵:最坏为O(n^3)邻接表:O(mn)
空间复杂度 邻接矩阵:O(n^2) 邻接表:O(m+n)

例子:

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

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

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

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

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

重新找个妹子(注意这个步骤和上面是一样的,这是一个递归的过程)

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

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

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

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

这就是匈牙利算法的流程,其中找妹子是个递归的过程,最最关键的字就是“腾”字===============================================================================

邻接表:o(mn)

羊吃草

theme:n只羊在一条长度为400的线段上,每只羊只会在它喜欢的区间[a,b]吃草,只会位于整数点处,且每时刻每个点只会有一只羊,先q次询问,每次询问区间[l,r]中最多有多少只羊吃草。1<=n,q<=400

solution:二分匹配。将羊与它喜欢的区间相连,则进行q次匹配,在匈牙利算法dfs为给定节点u配对是判断一下v是否位于[l,r]范围即可。

//匹配指定右区间
#include<bits/stdc++.h>
using namespace std;
#define re(i,n) for(int i=0;i<n;i++)
const int N = 445;
const int M = 445;
int a[N],b[N];
int linker[M];//记录右边节点v所选的左边节点的编号u
int used[M];//记录右边节点有没有被用
vector<int>G[N];
int n,m,q;
int l,r;

void createG()
{
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
        scanf("%d",&b[i]);
    for(int i=1;i<=n;i++)
    {
        for(int j=a[i];j<=b[i];++j)
            G[i].push_back(j);
    }
}

bool dfs(int u)
{
	for (int i=0;i<G[u].size();++i){    //扫描每个妹子
	    int v=G[u][i];
		if (v>=l&&v<=r && used[v]==false)
		{
			used[v]=1;
			if (linker[v]==0 || dfs(linker[v])) {名花无主或者能腾出个位置来,这里使用递归
				linker[v]=u;
				return true;
			}
		}
	}
	return false;
}

//匈牙利算法求二分图最大匹配
int hungary()
{
    int ans=0;
    fill(linker,linker+m+5,0);
    for(int u=1; u<=n; ++u)//给每个左节点找右节点
    {
        fill(used,used+m+5,0);
        if(dfs(u))//如果能成功给节点u找到一个匹配的右节点,且不影响之前选过的节点,则结果+1
            ans++;
    }
    return ans;
}

int main()
{
    cin>>n>>q;
    m=401;
    createG();
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d",&l,&r);
        int ans=hungary();
        printf("%d\n",ans);
    }
}

邻接矩阵:o(n^3)

poj1274:The Perfect Stall

theme:n只牛m个坑,每只牛一个坑,每个坑只能占一只牛,给出每只牛能取哪几个坑,问最多能安排好多少只牛占一个坑。0 <= N,M<= 200

solution:裸的二分图匹配

//theme:n只牛m个坑,每只牛一个坑,每个坑只能占一只牛,给出每只牛能取哪几个坑,问最多能安排好多少只牛占一个坑。0 <= N,M<= 200
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define re(i,n) for(int i=0;i<n;i++)
const int N = 305;
const int M = 305;
int linker[M];//记录右边节点v所选的左边节点的编号u
int used[M];//记录右边节点有没有被用
int G[N][M];//图
int n,m;

void createG()
{
    for(int u=1; u<=n; ++u)
    {
        int cnt;
        scanf("%d",&cnt);
        while(cnt--)
        {
            int v;
            scanf("%d",&v);
            G[u][v]=1;
        }
    }
}

bool dfs(int u)
{
	for (int v=1;v<=m;v++){    //扫描每个妹子
		if (G[u][v]==true && used[v]==false)
		{
			used[v]=1;
			if (linker[v]==0 || dfs(linker[v])) {名花无主或者能腾出个位置来,这里使用递归
				linker[v]=u;
				return true;
			}
		}
	}
	return false;
}

//匈牙利算法求二分图最大匹配
int hungary()
{
    int ans=0;
    fill(linker,linker+m+5,0);
    for(int u=1; u<=n; ++u)//给每个左节点找右节点
    {
        fill(used,used+m+5,0);
        if(dfs(u))//如果能成功给节点u找到一个匹配的右节点,且不影响之前选过的节点,则结果+1
            ans++;
    }
    return ans;
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        memset(G,0,sizeof(G));
        createG();
        int ans=hungary();
        printf("%d\n",ans);
    }
}

最大团

poj3692:Kindergarten

theme:n个男生,m个女生,其中女生之间、男生之间相互认识,有些女生与有些男生相互认识,问最能能从中选出多少人来是的任意两人相互认识?1 ≤ GB ≤ 200

solution:由题意可知是求最大团,最大团为选最大的点集使得任意两点之间都有边,而最大团=补图的最大独立集,最大独立集=总的点数-最大匹配

//theme:n个男生,m个女生,其中女生之间、男生之间相互认识,有些女生与有些男生相互认识,问最能能从中选出多少人来是的任意两人相互认识?1 ≤ G, B ≤ 200
//最大团
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define re(i,n) for(int i=0;i<n;i++)
const int N = 305;
const int M = 305;
int linker[M];//记录右边节点v所选的左边节点的编号u
int used[M];//记录右边节点有没有被用
int G[N][M];//图
int n,m,cnt;

//建补图
void createG()
{
    for(int i=1;i<=cnt;++i)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        G[u][v]=0;
    }
}

bool dfs(int u)
{
	for (int v=1;v<=m;v++){    //扫描每个妹子
		if (G[u][v]==true && used[v]==false)
		{
			used[v]=1;
			if (linker[v]==0 || dfs(linker[v])) {名花无主或者能腾出个位置来,这里使用递归
				linker[v]=u;
				return true;
			}
		}
	}
	return false;
}

//匈牙利算法求二分图最大匹配
int hungary()
{
    int ans=0;
    fill(linker,linker+m+5,0);
    for(int u=1; u<=n; ++u)//给每个左节点找右节点
    {
        fill(used,used+m+5,0);
        if(dfs(u))//如果能成功给节点u找到一个匹配的右节点,且不影响之前选过的节点,则结果+1
            ans++;
    }
    return ans;
}

int main()
{
    int now=0;
    while(~scanf("%d%d%d",&n,&m,&cnt)&&(n||m||cnt))
    {
        ++now;
        for(int i=1;i<=n;++i)
            for(int j=1;j<=m;++j)
                G[i][j]=1;
        createG();
        int x=hungary();
        int ans=n+m-x;
        printf("Case %d: %d\n",now,ans);
    }
}

二分多重匹配

hdu3605:Escape

theme:n个人,m间房,给出每个人能去哪几间房和房间最大容量,问这n个人能否都入住?1 <= n <= 100000, 1 <= m <= 10.

solution:二分多重匹配.

可以用网络流,加上一个源点与汇点。但节点数太大,可以考虑状态压缩,把n个人按能去的房间数组合,最多有2^10种

//theme:n个人,m间房,给出每个人能去哪几间房和房间最大容量,问这n个人能否都入住?1 <= n <= 100000, 1 <= m <= 10.
//邻接矩阵的匈牙利多重匹配(右节点有容量)
#include<iostream>
#include<cstdio>
using namespace std;
#define re(i,n) for(int i=0;i<n;i++)
const int N = 100005;
const int M = 11;
int linker[M][N];
int tem[M],num[M],used[M];//tmp表示当前右部顶点已匹配数目,num记录右边点最多能连几条边
int G[N][M];//图的邻接矩阵
int n,m;

bool dfs(int u)
{
    for(int v=0; v<m; v++)
    {
        if(G[u][v] && !used[v])
        {
            used[v] =1;
            if(tem[v] < num[v])
            {
                linker[v][tem[v]++] = u;
                return 1;
            }
            for(int i=0; i<tem[v]; ++i)
                if(dfs(linker[v][i]))
                {
                    linker[v][i] = u;
                    return 1;
                }
        }
    }
    return 0;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=0; i<n; ++i)
            for(int j=0; j<m; ++j)
                scanf("%d",&G[i][j]);
        for(int i=0; i<m; ++i)
            scanf("%d",&num[i]);
        for(int i=0; i<m; ++i)
            tem[i] = 0;
        int flag = 1;
        for(int i=0; i<n; ++i)
        {
            for(int j=0; j<m; ++j)
                used[j] = 0;
            if(!dfs(i))
            {
                flag = 0;
                break;
            }
        }
        puts(flag?"YES":"NO");
    }
}

 

 

 

 


匈牙利算法参考:https://blog.csdn.net/dark_scope/article/details/8880547

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值