二分图染色/最大二分图匹配方法总结

先给出二分图的定义:

一张图 G 是二分图当且仅当 G 的点集 V 可以分为两个点集 V0​,V1​,满足 V0​∪V1​=V,V0​∩V1​=∅,且对于 G 的每条边 e,其两个端点分别属于不同的点集。

简单来说,一张图是二分图,当且仅当它的点可以被分成两部分,而这张图上的所有边的两个端点,都属于两个不同的部分。

那么如何判断一张图是不是二分图呢?注意到每一条边的两个顶点都属于两个不同的集合,所以我们可以从任意一个点开始,将与其直接相连的点染成相反的颜色,用来代表两个不同的集合,并对其它点重复此过程,如果一张图染下来没有出现颜色矛盾,这就是一张二分图了。

bool dfs(ll u,ll co)
{
	color[u]=co;
	for(int i=head[u];i!=-1;i=edge[i].next)
	{
		ll y=edge[i].t;
		if(color[y]==co) return 0;
		if(color[y]==-co) return 1; 
		if(color[y]==0&&!dfs(y,-co)) return 0;
	}
	return 1;
}

接下来讲一讲最大二分图匹配(主要是刚发现dinic也可以解决这个问题。。。)

给出板子题

首先是最正宗的方法,匈牙利匹配。

大概的思路就是:(绿与被绿

处理左部图,看左边最多能有多少个点能匹配,那么那个就是答案。

遍历左部图,用一个时间戳来标记右部图中每一个点被标记的对应时间。每次从左边找一个点S,并从右边找一个与其相连的点S'。如果右边的这个点还没有被匹配。。。啥也别说,拉走!

如果不巧这个点S'已经有伴了,我们就可以选择绿了它的伴侣让它的伴侣A成人之美。如果它的伴侣A还能找到别的匹配,那大家皆大欢喜(也就是一个协商的过程),但是如果A没别人可以选了,那S也只能找下家,并重复上述操作知道找到匹配。如果找不到,那就找不到吧。。。

思路是这样,至于证明。。。指路别的博客

贴一个代码:(还算简单,因为上文中协商的过程可以用dfs来实现)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=101000;
#define endl '\n'
const ll inf=1<<30;
struct ty{
	ll t,next;
}edge[N<<1];
ll head[N];
ll cnt=0;
void add(ll a,ll b)
{
	edge[++cnt].t=b;
	edge[cnt].next=head[a];
	head[a]=cnt;
}
ll n,m,e;
ll a,b;
ll ti=0;//时间戳 
ll ans=0;
ll df[N];//被匹配的时间 
ll match[N];//匹配对象 
bool dfs(ll u,ll tim)
{
	for(int i=head[u];i!=-1;i=edge[i].next)
	{
		ll y=edge[i].t;
		if(df[y]==tim) continue;
		df[y]=tim;
		if(!match[y]/*还没配对,直接拉走*/||dfs(match[y],tim))
		{
			match[y]=u;
			return 1; 
		}
	}
	return 0;
}
int main()
{
	cin>>n>>m>>e;
	memset(head,-1,sizeof head);
	while(e--)
	{
	    cin>>a>>b;
		add(a,b);	
	} 
	for(int i=1;i<=n;++i)
	{
		if(dfs(i,++ti)) ans++;
	}
	cout<<ans<<endl;
	return 0;
}

然后是一个网络流的做法。对于每一条边,可以将其想象成一个管道,那么两个点相连,就是可以通过大小为1的流量。将左部图与一个超级源点相连(出水口),将右部图与一个超级聚点相连(进水口),那么就可以转化为最大流问题了。

那就是一个快乐的套板子了!

不过EK可能比较慢(好像比匈牙利还慢),dinic就很好

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=101000;
#define endl '\n'
const ll inf=1<<30;
struct ty{
	ll t,l,next;
}edge[N<<1];
ll cnt=1;//后面要让第i条边^1后能得到它的反向边(与它相邻)
ll head[N];
inline ll read(){
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    return x;
}
void add(ll a,ll b,ll c)
{
	edge[++cnt].t=b;
	edge[cnt].l=c;
	edge[cnt].next=head[a];
	head[a]=cnt;
}
ll inque[N];
ll dep[N];//用于分层
ll cur[N];//记录弧 
ll vis;//这次是否能到达终点
ll ans=0;
struct Pre
{
    ll v;//前一个节点
	ll edge;//前一条边	
}pre[N];
 
 
ll n,m,s,t,e;
ll a,b,c;  
 
bool bfs()//用于分层
{
//    memset(inque,0,sizeof inque);
//    memset(dep,0x3f,sizeof dep);
    for(int i=0;i<=t;++i)
    {
    	cur[i]=head[i];
    	dep[i]=0x3f3f3f3f3f3f3f3f;
    	inque[i]=0;
	}
    dep[s]=0;//起点
	queue<ll> q;
	q.push(s);
	while(!q.empty())
	{
		ll ty=q.front();
		q.pop();
		inque[ty]=0;//
		for(int i=head[ty];i!=-1;i=edge[i].next)
		{
			ll y=edge[i].t;
			if(dep[y]<=dep[ty]+1) continue;//无需更新
			if(edge[i].l==0) continue;
			dep[y]=dep[ty]+1;
			if(inque[y]) continue;//看是否在队里 
			inque[y]++;
			q.push(y); 
		} 
	}
	if(dep[t]!=0x3f3f3f3f3f3f3f3f) return 1;
	return 0;	
} 
 
ll dfs(ll u,ll flow)//节点,当前最小流量 
{
	ll rl=0;
	if(u==t)
	{
		vis=1;
		ans+=flow;
		return flow;
	} 
	ll used=0;//该点用过的流量 
	for(int i=cur[u]/*是cur!这里不是head(当前弧优化)*/;i!=-1;i=edge[i].next)
	{
		cur[u]=i;//修改当前弧 
		ll y=edge[i].t;
		if(dep[y]==dep[u]+1&&edge[i].l)
		{
			rl=dfs(y,min(flow-used,edge[i].l));//减去后的费用拿去比较 
			if(rl==0) continue;
			used+=rl; 
			edge[i].l-=rl;
			edge[i^1].l+=rl;
			if(used==flow) break;//满了 
			//return rl;
		}
	}
	return used; 
}
 
ll dic()
{
	while(bfs())
	{
		vis=1;
		while(vis)
		{
		    vis=0;
			dfs(s,inf);	
		} 
	}
	return ans;
}
int main()
{
	
	memset(head,-1,sizeof head);
	n=read();m=read();e=read();
	s=0;t=n+m+1;
	for(int i=1;i<=n;++i)
	{
		add(0,i,1);add(i,0,0);
	}
	for(int i=n+1;i<=t-1;++i)
	{
		add(i,t,1);add(t,i,0);
	}
	for(int i=1;i<=e;++i)
	{
		a=read();b=read();
		add(a,b+n,1);
		add(b+n,a,0);//添加反向边 
	}
	cout<<dic()<<endl;
	return 0;
}

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
人工智能关于最大二分图的程序代码: #include<stdio.h> #include<string.h> main() { bool map[100][300]; int i,i1,i2,num,num1,que[300],cou,stu,match1[100],match2[300],pque,p1,now,prev[300],n; scanf("%d",&n); for(i=0;i<n;i++) { scanf("%d%d",&cou;,&stu;); memset(map,0,sizeof(map)); for(i1=0;i1<cou;i1++) { scanf("%d",&num;); for(i2=0;i2<num;i2++) { scanf("%d",&num1;); map[i1][num1-1]=true; } } num=0; memset(match1,int(-1),sizeof(match1)); memset(match2,int(-1),sizeof(match2)); for(i1=0;i1<cou;i1++) { p1=0; pque=0; for(i2=0;i2<stu;i2++) { if(map[i1][i2]) { prev[i2]=-1; que[pque++]=i2; } else prev[i2]=-2; } while(p1<pque) { now=que[p1]; if(match2[now]==-1) break; p1++; for(i2=0;i2<stu;i2++) { if(prev[i2]==-2&&map;[match2[now]][i2]) { prev[i2]=now; que[pque++]=i2; } } } if(p1==pque) continue; while(prev[now]>=0) { match1[match2[prev[now]]]=now; match2[now]=match2[prev[now]]; now=prev[now]; } match2[now]=i1; match1[i1]=now; num++; } if(num==cou) printf("YES\n"); else printf("NO\n"); } } dfs实现过程: #include<stdio.h> #include<string.h> #define MAX 100 bool map[MAX][MAX],searched[MAX]; int prev[MAX],m,n; bool dfs(int data) { int i,temp; for(i=0;i<m;i++) { if(map[data][i]&&!searched[i]) { searched[i]=true; temp=prev[i]; prev[i]=data; if(temp==-1||dfs(temp)) return true; prev[i]=temp; } } return false; } main() { int num,i,k,temp1,temp2,job; while(scanf("%d",&n)!=EOF&&n!=0) { scanf("%d%d",&m,&k); memset(map,0,sizeof(map)); memset(prev,int(-1),sizeof(prev)); memset(searched,0,sizeof(searched)); for(i=0;i<k;i++) { scanf("%d%d%d",&job;,&temp1;,&temp2;); if(temp1!=0&&temp2;!=0) map[temp1][temp2]=true; } num=0; for(i=0;i<n;i++) { memset(searched,0,sizeof(searched)); dfs(i); } for(i=0;i<m;i++) { if(prev[i]!=-1) num++; } printf("%d\n",num); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值