zoukankan      html  css  js  c++  java
  • 知识点简单总结——带花树(一般图最大匹配)

    知识点简单总结——带花树(一般图最大匹配)

    前置知识

    二分图最大匹配(匈牙利算法)

    一般图最大匹配

    首先思考一下一般图和二分图的区别在哪里。

    很明显二分图没有奇环。

    那么只要能处理好奇环的结果就好。

    首先像匈牙利算法一样每次选择一个点开始匹配并进行一次新的黑白染色,起点为黑色。

    进行bfs,用队列处理:对于每个黑点 $ u $ ,枚举所有邻点 $ v $ ,分情况讨论:

    1. $ v $ 没染色,则将其染成白色,之后如果 $ v $ 已经有匹配,则直接将 $ v $ 的匹配点染黑加入队列,否则由 $ v -> u $ 方向不断更新匹配,类似于匈牙利算法。
    2. $ v $ 已经是白点,忽略。
    3. $ v $ 是黑点时,说明出现奇环,准备进行缩点操作(开花)
    4. 已经被缩进了同一个点,直接忽略

    具体看下代码:

    int hun(int sp)
    {
    	memset(col,0,sizeof(col)),memset(pre,0,sizeof(pre));
    	for(int i=1;i<=n;i++) f[i]=i;while(!q.empty()) q.pop();
    	q.push(sp),col[sp]=1;
    	while(!q.empty())
    	{
    		int x=q.front();q.pop();
    		for(int i=he[x],t=e[i].to;i;i=e[i].ne,t=e[i].to)if(col[t]!=2&&find(x)!=find(t))
    		{
    			if(!col[t])
    			{
    				col[t]=2,pre[t]=x;
    				if(!tar[t])
    				{
    					int lst=0;while(t) lst=tar[pre[t]],tar[t]=pre[t],tar[pre[t]]=t,t=lst;
    					return 1;
    				}else col[tar[t]]=1,q.push(tar[t]);
    			}else{int z=lca(x,t);uno(x,t,z),uno(t,x,z);}
    		}
    	}
    	return 0;
    }
    

    其中 $ tar $ 代表匹配对象, $ pre $ 代表前驱,具体点说类似于为匈牙利退流保留的预选方案,在这份代码里 $ pre $ 边由白点指向黑点。

    在枚举到一个没有染色且无匹配的 $ v $ 时,将其染色后,不断向上更新,每次将 $ v,pre[v] $ 匹配,之后将 $ v $ 置为 $ tar[pre[v]] $ 继续往回重复进行更新匹配过程,和匈牙利算法差不多。

    缩点操作

    毫无疑问奇环上至少有一个点没得在内部匹配,所以可以考虑缩成一个点,只有需要匈牙利匹配的路径反悔操作时候再打开。

    维护一个并查集合并被缩的点。

    记录每个点的匹配对象( $ tar $ )和前驱( $ pre $ ),这些都基于原有的边。

    缩点之后的图和上述的边组成的是一棵树。

    那么在树上就可以用朴素方法找lca:

    int lca(int x,int y)
    {
    	da++;x=find(x),y=find(y);
    	while(dep[x]!=da){dep[x]=da,x=find(pre[tar[x]]);if(y) swap(x,y);}
    	return x;
    }
    

    这哪里朴素了。

    就是不断沿着两种边向上,等两点都经过过某点则为lca。

    然后就是缩点处理:

    void uno(int x,int y,int z)
    {
    	while(find(x)!=z)
    	{
    		pre[x]=y,y=tar[x];
    		if(col[y]==2) col[y]=1,q.push(y);
    		if(find(x)==x) f[x]=z;if(find(y)==y) f[y]=z;
    		x=pre[y];
    	}
    }
    

    大概就是:

    1. 默认花里只有lca不进行内部匹配,一路沿着 $ pre $ 跳花边,将环上原黑点也向原白点连 $ pre $ 边。
    2. 把所有白点染黑然后进入队列增广。
    3. 并查集全部合并到lca上。

    好难,死活折腾不明白(悲)

    uoj79

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long lint;
    struct pat{int x,y;pat(int x=0,int y=0):x(x),y(y){}bool operator<(const pat &p)const{return x==p.x?y<p.y:x<p.x;}};
    template<typename TP>inline void read(TP &tar)
    {
    	TP ret=0,f=1;char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9'){ret=ret*10+(ch-'0');ch=getchar();}
    	tar=ret*f;
    }
    namespace RKK
    {
    const int N=511,M=270011;
    struct sumireko{int to,ne;}e[M];int he[N],ecnt;
    void addline(int f,int t){e[++ecnt].to=t;e[ecnt].ne=he[f],he[f]=ecnt;}
    int n,m,tar[N];
    int f[N];int find(int x){return f[x]==f[f[x]]?f[x]:f[x]=find(f[x]);}
    int dep[N],da;
    int pre[N],col[N];
    queue<int>q;
    int lca(int x,int y)
    {
    	da++;x=find(x),y=find(y);
    	while(dep[x]!=da){dep[x]=da,x=find(pre[tar[x]]);if(y) swap(x,y);}
    	return x;
    }
    void uno(int x,int y,int z)
    {
    	while(find(x)!=z)
    	{
    		pre[x]=y,y=tar[x];
    		if(col[y]==2) col[y]=1,q.push(y);
    		if(find(x)==x) f[x]=z;if(find(y)==y) f[y]=z;
    		x=pre[y];
    	}
    }
    int hun(int sp)
    {
    	memset(col,0,sizeof(col)),memset(pre,0,sizeof(pre));
    	for(int i=1;i<=n;i++) f[i]=i;while(!q.empty()) q.pop();
    	q.push(sp),col[sp]=1;
    	while(!q.empty())
    	{
    		int x=q.front();q.pop();
    		for(int i=he[x],t=e[i].to;i;i=e[i].ne,t=e[i].to)if(col[t]!=2&&find(x)!=find(t))
    		{
    			if(!col[t])
    			{
    				col[t]=2,pre[t]=x;
    				if(!tar[t])
    				{
    					int lst=0;while(t) lst=tar[pre[t]],tar[t]=pre[t],tar[pre[t]]=t,t=lst;
    					return 1;
    				}else col[tar[t]]=1,q.push(tar[t]);
    			}else{int z=lca(x,t);uno(x,t,z),uno(t,x,z);}
    		}
    	}
    	return 0;
    }
    int main()
    {
    	read(n),read(m);for(int i=1,x,y;i<=m;i++) read(x),read(y),addline(x,y),addline(y,x);
    	int ans=0;for(int i=1;i<=n;i++)if(!tar[i]) ans+=hun(i);
    	printf("%d
    ",ans);for(int i=1;i<=n;i++) printf("%d ",tar[i]);putchar('
    ');
    	return 0;
    }
    }
    int main(){return RKK::main();}
    

    应用

    不是很明显了嘛。

  • 相关阅读:
    【QT】Linux下安装QT开发环境
    【C#】Winform嵌入dll到exe中
    使用maven插件构建docker镜像并推送到阿里镜像仓库
    蓝牙耳机连接 win10音量异常
    max virtual memory areas vm.max_map_count [65530] is too low
    docker 安装 confluence
    jenkins docker push脚本
    docker 删除指定名称的所有容器
    docker 安装 xxl-job-admin
    docker 安装 sentinel
  • 原文地址:https://www.cnblogs.com/rikurika/p/13364797.html
Copyright © 2011-2022 走看看