带花树

弥补下暑假打多校的遗憾

就是一般图匹配

二分图没有奇环,而一般图有。所以匈牙利算法中的寻找增广路然后路径取反的方法在一般图上就不适用了。

带花树是要解决奇环的问题。
一个奇环里至少有一个点不能匹配,那就干脆把一个奇环缩成一个点,路径取反的时候再暴力展开一个个取反。所以 复杂度不是很友好 n 3 n^3 n3

详细流程:

我们给所有点黑白染色。假设开始增广的点是黑点。
把所有黑点压进队列中顺次处理。对于一个黑点u,找与他相邻的点v,会出现一下几种情况:

  1. u,v已经被缩成一个点了,就不管它。
  2. v是白点,说明已经被匹配了,也不管。
  3. v还没有被染色。那就先把这个点染成白的,然后尝试去与他匹配。如果v还没有匹配就匹配上,增广成功,然后一路跳回取反。如果v已经被匹配了,那么匹配他的点就是个黑点,染色,然后压进队列。
  4. v也是黑点。这时候染色发生了冲突,说明遇见了奇环。这时候就需要找到两个点的lca,然后把这整个环缩成一个点。这就是开花,挺无聊

怎样把环缩成一个点:

  1. 找x和y的LCA
  2. 在pre数组中把x和y接起来(表示它们形成环了!)
  3. 从x、y分别走到p,修改并查集使得它们都变成一家人,同时沿路把pre数组接起来。

模板题:题目

#include<bits/stdc++.h>
#define ks ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
#define fr first
#define sc second
#define pb push_back
#define pf push_front
#define mp make_pair
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int>pa;
typedef set<int>::iterator sit;
typedef multiset<int>::iterator msit;
template<class T>inline void read(T &res){
    char c;T flag=1;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
    while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}
void wenjian(){freopen("concatenation.in","r",stdin);freopen("concatenation.out","w",stdout);}
void tempwj(){freopen("hash.in","r",stdin);freopen("hash.out","w",stdout);}
ll gcd(ll a,ll b){return b == 0 ? a : gcd(b,a % b);}
ll qpow(ll a,ll b,ll mod){a %= mod;ll ans = 1;while(b){if(b & 1)ans = ans * a % mod;a = a * a % mod;b >>= 1;}return ans;}
struct chongzai{int c; bool operator<(const chongzai &b )const{ return c>b.c; } }sss;
const int maxn=1e6+177;
const int maxm=1e6+177;
const ll mod=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
using namespace std;

int n,m;
int match[maxn],pre[maxn],vis[maxn],fa[maxn],tim[maxn],idx,ans;

queue<int>qu;
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}

struct Node{
	int next;
	int to;
	int vul;
}edge[maxn];

int head[maxn];
int tot;
void add(int from,int to){
	edge[++tot].next=head[from];
	edge[tot].to=to;
	head[from]=tot;
}



int lca(int x,int y)
{
	for (++idx;;swap(x,y))
		if (x)
		{
			x=find(x);
			if (tim[x]==idx) return x;
			else tim[x]=idx,x=pre[match[x]];
		}
}
void check(int x,int y,int p)
{
	while (find(x)!=p)
	{
		pre[x]=y;y=match[x];
		if (vis[y]==2) vis[y]=1,qu.push(y);
		if (find(x)==x) fa[x]=p;
		if (find(y)==y) fa[y]=p;
		x=pre[y];
	}
}
int daihua(int s)
{
	for (int i=1;i<=n;++i){
        vis[i]=0;
        pre[i]=0;
        fa[i]=i;
	}
	while(!qu.empty()){
        qu.pop();
	}
	qu.push(s);
	vis[s]=1;
	while(!qu.empty()){
		int x=qu.front();
		qu.pop();
		for(int i=head[x];i;i=edge[i].next){
			int y=edge[i].to;
			if (find(x)==find(y)||vis[y]==2) continue;
			if (!vis[y])
			{
				vis[y]=2;
				pre[y]=x;
				if (!match[y]){
					for (int k=y,lst;k;k=lst){
                        lst=match[pre[k]],match[k]=pre[k],match[pre[k]]=k;
					}

					return 1;
				}
				vis[match[y]]=1;
				qu.push(match[y]);
			}else{
				int p=lca(x,y);
				check(x,y,p);
				check(y,x,p);
			}
		}
	}
	return 0;
}

void deal(){
    for (int i=1;i<=n;i++){
        if (!match[i]){
            ans+=daihua(i);
        }
	}
	printf("%d\n",ans);
	for (int i=1;i<=n;i++){
        printf("%d ",match[i]);
	}
	printf("\n");

}
int main(){
	scanf("%d%d",&n,&m);
	int be,en;
	for (int i=1;i<=m;++i){
		scanf("%d%d",&be,&en);
		add(be,en);
		add(en,be);
	}
	deal();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值