2021——二分图匹配

1.最小点覆盖=最大匹配

eg.1 poj1325 Machine Schedule

题意:有两个机器A和B,A机器有n个模式,B机器有m个模式,两个机器最初在0模式。有k个作业,每个作业有三个参数i,a,b,其中i代表作业编号,a和b代表第i作业要么在A机器的a模式下完成或在B机器的b模式下完成,问两个机器总共最少变换多少次可以完成所有作业

 

我们将两台机器的每个模式作为一个顶点,如果作业作业需要机器A的x模式和机器B的y模式,就将x-y相连,然后用最少的顶点覆盖所有的边即可

 

代码

// 最小点覆盖
// by andyc_03 
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
int n,m,ma[305][305],link[305],vis[305];
int dfs(int x)
{
	for(int i=1;i<=m;i++)
	{
		if(!vis[i] && ma[x][i]==1)
		{
			vis[i]=1;
			if(!link[i] || dfs(link[i])==1)
			{
				link[i]=x;
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	freopen("a.in","r",stdin);
	int t; 
	while(scanf("%d%d%d",&n,&m,&t)!=EOF && n)
	{
		memset(ma,0,sizeof(ma));
		memset(link,0,sizeof(link));
		int x,y,z;
		for(int i=1;i<=t;i++)
		{
			scanf("%d%d%d",&x,&y,&z);
			ma[y][z]=1;
		}
		int cnt=0;
		for(int i=1;i<=n;i++)
		{
			memset(vis,0,sizeof(vis));
			if(dfs(i)) cnt++;
		}
		printf("%d\n",cnt);
	}
	return 0;
}

 

eg.2  poj3041 Asteroids

题意:有一个N*N的网格,该网格有K个障碍物.你有一把武器,每次你使用武器可以清除该网格特定行或列的所有障碍.问你最少需要使用多少次武器能清除网格的所有障碍物?

 

可以将行作为左侧部分,列作为右侧部分,若节点(x,y)有障碍,那么将x-y连边,然后就是需要求最小点覆盖

 

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
int n,k;
const int maxn=1005;
const int maxm=2e4+5;
struct edge
{
	int to,nxt;
}e[maxm];
int cnt,head[maxn];
void add(int x,int y)
{
	e[++cnt].to=y;
	e[cnt].nxt=head[x];
	head[x]=cnt;
}
int link[maxn],vis[maxn];
bool dfs(int x)
{
	for(int i=head[x];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(!vis[to])
		{
			vis[to]=1;
			if(link[to]==-1 || dfs(link[to])==1)
			{
				link[to]=x;
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	while(~scanf("%d%d",&n,&k))
	{
		int x,y;
		for(int i=1;i<=k;i++)
		{
			scanf("%d%d",&x,&y);
			add(x,n+y);
			add(n+y,x);
		}
		memset(link,-1,sizeof(link));
		int res=0;
		for(int i=1;i<=n;i++)
		{
			memset(vis,0,sizeof(vis));
			res+=dfs(i);
		}
		printf("%d\n",res);
	}
	
	return 0;
}

 

eg.3 poj2226 Muddy Fields

题意:一个由r行c列方格组成的田地,里面有若干个方格充满泥泞,其余方格都是草。要用长度不限,宽度为1的长木板来覆盖这些泥方格,但不能覆盖草地。最少要用多少个长木板。

 

将连着的竖着或者横着的一排泥泞点编成一个号,然后将每个点竖着和横着的点的号连边,求最小点覆盖

 

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
int n,m;
int ma[55][55],num[55][55],numm[55][55];
const int maxn=2505;
const int maxm=6250005;
struct edge
{
	int to,nxt;
}e[maxm<<1];
int cnt,head[maxn];
void add(int x,int y)
{
	e[++cnt].to=y;
	e[cnt].nxt=head[x];
	head[x]=cnt;
}
int link[maxn],vis[maxn];
bool dfs(int x)
{
	for(int i=head[x];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(!vis[to])
		{
			vis[to]=1;
			if(link[to]==-1 || dfs(link[to])==1)
			{
				link[to]=x;
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	while(~scanf("%d%d",&n,&m))
	{
		for(int i=1;i<=n;i++)
		{
			getchar();
			for(int j=1;j<=m;j++)
			{
				char cc=getchar();
				if(cc=='*') ma[i][j]=1;
				else ma[i][j]=0;
			}
		}
		int cnt,cntt;
		for(int i=1;i<=n;i++)
		{
			int flag=1;
			for(int j=1;j<=m;j++)
			{
				if(ma[i][j]==1)
				{
					if(flag) cnt++;
					flag=0;
					num[i][j]=cnt;
				}
				else flag=1;
			}
		}
		cntt=cnt;
		for(int i=1;i<=m;i++)
		{
			int flag=1;
			for(int j=1;j<=n;j++)
			{
				if(ma[j][i]==1)
				{
					if(flag) cnt++;
					flag=0;
					numm[j][i]=cnt;
				}
				else flag=1;
			}
		}
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				if(ma[i][j]==1)
					add(num[i][j],numm[i][j]),add(numm[i][j],num[i][j]);
		memset(link,-1,sizeof(link));
		int res=0;
		for(int i=1;i<=cntt;i++)
		{
			memset(vis,0,sizeof(vis));
			res+=dfs(i);
		}
		printf("%d\n",res);
	}
	
	return 0;
}

 

2.最小边覆盖

 

eg.1 poj3020 Antenna Placement

 

题意:

 *--代表城市,o--代表空地

给城市安装无线网,一个无线网最多可以覆盖两座城市,问覆盖所有城市最少要用多少无线网。

 

我们可以利用交替染色的思想,这样1*2无线网就相当于是颜色1和颜色2的最大匹配数,那么我们所求即可转化为最小边覆盖

 

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
int n,m,flag[405],vis[405];
int ma[42][12],col[42][12],num[42][12];
const int maxm=405*405;
const int maxn=405;
int head[405],tot=0,link[405];
struct edge
{
	int to,nxt;
}e[maxm<<1];
void add(int x,int y)
{
	e[++tot].to=y;
	e[tot].nxt=head[x];
	head[x]=tot;
	e[++tot].to=x;
	e[tot].nxt=head[y];
	head[y]=tot;
}
int dfs(int x)
{
	for(int i=head[x];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(!vis[to])
		{
			vis[to]=1;
			if(link[to]==-1 || dfs(link[to]))
			{
				link[to]=x;
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	int t; scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m); tot=0;
		memset(col,0,sizeof(col));
		memset(num,0,sizeof(num));
		memset(ma,0,sizeof(ma));
		memset(flag,0,sizeof(flag));
		memset(link,-1,sizeof(link));
		memset(head,0,sizeof(head));
		int res=0,cnt=0;
		for(int i=1;i<=n;i++)
		{
			getchar();
			for(int j=1;j<=m;j++)
			{
				char cc=getchar();
				if(cc=='*') res++,ma[i][j]=1,num[i][j]=++cnt;
				else ma[i][j]=0;
			}
		}
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				col[i][j]=(j+i-1)%2;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
			{
				if(ma[i][j]==1 && ma[i][j+1]==1) 
				{
					if(col[i][j]==1) flag[num[i][j]]=1;
					if(col[i][j+1]==1) flag[num[i][j+1]]=1;
					add(num[i][j],num[i][j+1]);
				}
				if(ma[i][j]==1 && ma[i+1][j]==1) 
				{
					if(col[i][j]==1) flag[num[i][j]]=1;
					if(col[i+1][j]==1) flag[num[i+1][j]]=1;
					add(num[i][j],num[i+1][j]);
				}
			}	
		for(int i=1;i<=cnt;i++)
		{
			if(!flag[i]) continue;
			memset(vis,0,sizeof(vis));
			res-=dfs(i);
		}
		printf("%d\n",res);
	}
	return 0;
}

 

3.最大点独立集

 

eg.1 poj1466 Girls and Boys

 

题意:有n个学生,并且给出男女之间的暧昧关系。现在求一个学习小组,使得里面的任何两个人之间不能有暧昧关系。求这个学习小组最多能够有多少人?

 

最大点独立集=n-最大匹配

 

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
int n,ma[505][505],link[505],vis[505];
int dfs(int x)
{
	for(int i=1;i<=n;i++)
	{
		if(!vis[i] && ma[x][i]==1)
		{
			vis[i]=1;
			if(!link[i] || dfs(link[i])==1)
			{
				link[i]=x;
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	freopen("a.in","r",stdin);
	while(scanf("%d",&n)!=EOF)
	{
		memset(ma,0,sizeof(ma));
		memset(link,0,sizeof(link));
		int k,x,y;
		for(int i=1;i<=n;i++)
		{
			scanf("%d: (%d)",&k,&x);
			k++;
			for(int j=1;j<=x;j++)
			{
				scanf("%d",&y);
				y++;
				ma[k][y]=1;
			}	
		}
		int cnt=0;
		for(int i=1;i<=n;i++)
		{
			memset(vis,0,sizeof(vis));
			if(dfs(i)) cnt++;
		}
		printf("%d\n",n-cnt/2);
	}
	return 0;
}

 

4.二分图最大权匹配——KM算法

 

KM算法,用于求二分图匹配的最佳匹配。最佳匹配,就是带权二分图的权值最大的完备匹配称为最佳匹配。 完备匹配是X部中的每一个顶点都与Y部中的一个顶点匹配,或者Y部中的每一个顶点也与X部中的一个顶点匹配。

对于原图中的任意一个结点,给定一个顶标值,使得X_{i}+Y_{i}\geqslant W_{i,j}

我们可以得到KM算法流程:

  1. 初始化可行顶标的值 (设定X_{i}=W_{i,j},Y_{i}=0)
  2. 用匈牙利算法寻找相等子图的完备匹配
  3. 若未找到增广路则修改可行顶标的值
  4. 重复(2)(3)直到找到相等子图的完备匹配为止

下面就具体的说说第3步中修改顶标的方法:

正在增广的增广路径上属于集合X的所有点减去一个常数dt,属于集合Y的所有点加上一个常数dt,这样原来的相等子图依旧在相等子图里。

dt的取值:如果dt太小会导致没有新的可行边加入,如果dt太大会导致X_{i}+Y_{i}\geqslant W_{i,j}不再成立,所以我们可以取dt=min(X_{i}+Y_{j}-W_{i,j}),这样能保证每次至少有一条边加入新的相等子图

 

这样做的时间复杂度为 O(N^{4}),继续考虑优化,如果在记录一个slack数组,用于记录最优的dt,每次增广的过程中再进行修改,这样的时间复杂度看起来就是O(N^{3}),但是,还是可以构造匹配的部分跑到O(N^{2})的数据,所以最坏的时间复杂度仍然是O(N^{4})

但是!! 如果我们把dfs改成bfs,它的复杂度就会真正降到O(N^{3})

 

代码

#include<bits/stdc++.h>
using namespace std;
int n,m;
typedef long long ll;
const int maxn=505;
const int maxm=125005;
const ll inf=1e18;
struct edge
{
	int to,nxt,v;
}e[maxm<<1];
ll ma[maxn][maxn],ex[maxn],ey[maxn];
int link[maxn],vis[maxn];
ll slack[maxn],pre[maxn];
void match(int u)
{
	int x,y=0,yy;
	ll dt;
	memset(pre,0,sizeof(pre));
	for(int i=1;i<=n;i++) slack[i]=inf;
	link[y]=u;
	while(1)
	{
		x=link[y];
		dt=inf;
		vis[y]=1;
		for(int i=1;i<=n;i++)
		{
			if(vis[i]) continue;
			if(slack[i]>ex[x]+ey[i]-ma[x][i])
			{
				slack[i]=ex[x]+ey[i]-ma[x][i];
				pre[i]=y;
			}
			if(slack[i]<dt) dt=slack[i],yy=i;
		}
		for(int i=0;i<=n;i++)
		{
			if(vis[i]) ex[link[i]]-=dt,ey[i]+=dt;
			else slack[i]-=dt;
		}
		y=yy;
		if(link[y]==-1) break;
	}
	while(y)
	{
		link[y]=link[pre[y]];
		y=pre[y];
	}
}
ll KM()
{
	memset(link,-1,sizeof(link));
	for(int i=1;i<=n;i++)
	{
		memset(vis,0,sizeof(vis));
		match(i);
	}
	ll res=0;
	for(int i=1;i<=n;i++)
		if(link[i]!=-1) 
			res+=ma[link[i]][i];
	return res;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	scanf("%d%d",&n,&m);
	int x,y,z;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			ma[i][j]=-inf;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		ma[x][y]=z;
	}
	printf("%lld\n",KM());
	for(int i=1;i<=n;i++)
		printf("%lld ",link[i]);
	return 0;	
} 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值