二分图匹配 一般图匹配 带花树

一些常用概念和定理:

Hall定理:

  二部图G中的两部分顶点组成的集合分别为X, Y; X={X1, X2, X3,X4, .........,Xm}, Y={y1, y2, y3, y4 , .........,yn}, G中有一组无公共点的边,一端恰好为组成X的点的充分必要条件是:X中的任意k个点至少与Y中的k个点相邻。(1≤k≤m)

匹配:

  给定一个二分图G,在G的一个子图M中,M的边集中的任意两条边都不依附于同一个顶点,则称M是一个匹配。图一中红线为就是一组匹配。

未盖点:

  设Vi是图G的一个顶点,如果Vi 不与任意一条属于匹配M的边相关联,就称Vi 是一个未盖点。如图一中的a3、b1。

交错路:

  设P是图G的一条路,如果P的任意两条相邻的边一定是一条属于M而另一条不属于M,就称P是一条交错路。如图一中a2->b2->a1->b4。

可增广路:

  两个端点都是未盖点的交错路叫做可增广路。

顶点的数目:

  图中顶点的总数。

最大独立数(也就是最大独立集和最大团):

  从V个顶点中选出k个顶,使得这k个顶互不相邻。 那么最大的k就是这个图的最大独立数。

ps:最大团问题是NP问题,但是二分图中的最大团可以通过多项式算法求解

最小顶点覆盖数:

  用最少的顶点数k来覆盖图的所有的边,k就是这个图的最小顶点覆盖数。

最大匹配数:

  所有匹配中包含的边数最多的数目称为最大匹配数。

顶点的数目=最大独立数+最小顶点覆盖数(对于所有无向图都有效)

最大匹配数=最小顶点覆盖数(只对二分图有效)

完全图:也就是任意两点都有边的图

完全子图:就是任意两都有边的子图

算法的实现方法:

不断寻找可增广路知道找不到为止,对此我想说,可以从很明显的奇偶找数特征入手。

增广路的一些性质:

     (1)有奇数条边。
  (2)起点在二分图的左半边,终点在右半边。
  (3)路径上的点一定是一个在左半边,一个在右半边,交替出现。
  (4)整条路径上没有重复的点。
  (5)路径上的所有第奇数条边都不在原匹配中,所有第偶数条边都出现在原匹配中。
  (6)把增广路径上的所有第奇数条边加入到原匹配中去,并把增广路径中的所有第偶数条边从原匹配中删除(这个操作称为增广路径的取反,即是俗说的异或操作),则新的匹配数就比原匹配数加了1个。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int dfs(int x)
{
	use[x]=1;
	int t=a[x].size();
	for(int i=0;i<t;i++)
	{
		int u=a[x][i],w=match[u];
		if(w<0 || !use[w] && dfs(w))
		{
			match[x]=u;
			macth[u]=x;
			return 1;
		}
	}
	return 0;
}
int zuidapipei()
{
	int res=0;
	memset(match,-1,sizeof(match));
	for(int i=1;i<=n;i++)
	{
        if(match[i]!=-1) continue;
		memset(use,0,sizeof(use));
		if(dfs(i))
		{
			res++;
		}
	}
	return res;
}

最小路径覆盖数问题:

可以把每一个顶点一分为二,可以理解为是出发和目标,然后按照原来的图进行连接转化为二分图,最后求最大匹配即可,其中ans=顶点数-最大匹配数。

简单说明:当一条边也没有是,无疑顶点数=路径数,没出现一对匹配路径数就会减一,每一条二分图上的边都会使路径数减一,也就是二分图上的

每一条边都可以将两条路径和为一条。

一般图匹配:用带花树算法实现(也可以用tutte矩阵,然并卵我不会

相对于二分图匹配不能用匈牙利dfs增广的原因是,遇到奇环时增广会出现同一个点连接两条匹配边的事情,然后匹配翻转取反的时候就会出现矛盾,无法保证

算方法的正确性。针对这个问题,带花树算法将奇环处理成一个缩点,这并不是显性的缩点(显性的缩点不便于环内的匹配),而是用并查集维护这个出现的每一朵花(也就是奇环)。

同时为了匹配边取反我们还需要记录每一个点的前驱。

另外,环上的前驱是要双向的(据说是因为在环上不确定增广的方向

同时还要将环上所有顶点都染成同一种颜色才行(缩点嘛

奇环一共2k+1个点,由于环里的一个点一定会向外匹配,这样环中的另外2k个点就能活跃起来进行匹配了。。。(能形成k条边呢

那么加入奇环就是终点了呢,不会有向外匹配的点了呢?这样环中还是会有2k个点剩余,不影响最终的答案。

至于为什么要用bfs呢(似乎是因为交错树的原因,这种写法实现这种算法更加方便。

贴一下我的带花树板子

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<climits>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
const int maxn=1005;
int n,m;
int biao[maxn],vs[maxn],fa[maxn],match[maxn],pre[maxn];//pre是非匹配边的前置点
vector<int> a[maxn];
queue<int> que;
inline const int Get_Int() {
	int num=0,bj=1;
	char x=getchar();
	while(x<'0'||x>'9') {
		if(x=='-')bj=-1;
		x=getchar();
	}
	while(x>='0'&&x<='9') {
		num=num*10+x-'0';
		x=getchar();
	}
	return num*bj;
}
int lca(int x,int y)
{
    static int times=0;
    ++times;
    x=fa[x],y=fa[y];
    while(vs[x]!=times)
    {
    	if(x)
    	{ 
        vs[x]=times;
        x=fa[pre[match[x]]];
        } 
		swap(x,y);
    }
    return x;
}
void bloom(int x,int y,int z)//标记花,给双向标记
{
    while(fa[x]!=z)
    {
        pre[x]=y;
        y=match[x];
        if(biao[y]==1)
        {
            biao[y]==0;
			que.push(y);
        }
        fa[x]=fa[y]=fa[z];//万一是个花的嵌套怎么办
        x=pre[y];
    }
}
int bfs(int s)
{
    for(int i=1;i<=n;i++) fa[i]=i;
//    fill(biao,biao+n+1,-1);
     memset(biao,-1,sizeof(biao));
    que=queue<int>();
    biao[s]=0;
    que.push(s);
    while(!que.empty())
    {
        int t=que.front();
//        cout<<"t="<<t<<endl;
		que.pop();
        for(int nex:a[t])
        {
            if(biao[nex]==-1)
            {
                biao[nex]=1;
                pre[nex]=t;
                if(match[nex]==0)
                {
                    for(int to=nex,from=t;to;from=pre[to])
                    {
                        match[to]=from,swap(match[from],to);
//                        cout<<match[u]<<" "ma
                    }
                    return true;
                }
                biao[match[nex]]=0;
                que.push(match[nex]);
            }
            else if(biao[nex]==0&&fa[t]!=fa[nex])
            {
//            	cout<<"找到花了"<<endl; 
                int gong=lca(t,nex);
                bloom(t,nex,gong);
				bloom(nex,t,gong);
            }
        }
    }
    return false;
}
//int n,m;
int main()
{
//	freopen("in.txt","r",stdin);
//    cin>>n>>m;
       n=Get_Int();
	   m=Get_Int();
    for(int i=1;i<=m;i++)
    {
        int l,r;
          l=Get_Int();
		  r=Get_Int();
        a[l].push_back(r),a[r].push_back(l);
    }
    int res=0;
    for(int i=n;i>=1;i--)
    {
        if(!match[i]) 
        {
            res+=bfs(i);
        }
    }
    printf("%d\n",res);
    for(int i=1;i<=n;i++)
    if(i!=n) printf("%d ",match[i]);
    else printf("%d\n",match[i]);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值