背景知识介绍
二分图的最大匹配
描述
给定一个二分图,其中左半部包含n1
个点(编号 1∼n1
),右半部包含n2
个点(编号 1∼n2
),二分图共包含m
条边。
数据保证任意一条边的两个端点都不可能在同一部分中。
请你求出二分图的最大匹配数。
二分图的匹配:给定一个二分图G
,在G
的一个子图M
中,M
的边集{E}
中的任意两条边都不依附于同一个顶点,则称M
是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。
输入
第一行包含三个整数n1
、n2
和m
。
接下来m
行,每行包含两个整数u
和v
,表示左半部点集中的点u
和右半部点集中的点v
之间存在一条边。
数据范围
1≤n1,n2≤500
1≤u≤n1
1≤v≤n2
1≤m≤10^5
输出
输出一个整数,表示二分图的最大匹配数。
代码实现
#include<iostream>
#include<cstring>
using namespace std;
const int N=505,M=100005;
int n1,n2,m;
int h[N],e[M],ne[M],idx;
int match[N];//存储匹配节点
bool st[N];//顶点是否已在当前增广路径中
void add(int u,int v)
{
e[idx]=v,ne[idx]=h[u],h[u]=idx++;
}
bool find(int x)//为x寻找匹配
{
for(int i=h[x];~i;i=ne[i])
{
int j=e[i];
if(!st[j])
{
st[j]=true;
if(match[j]==0||find(match[j]))//如果j未匹配或j的匹配节点可以重新匹配
{
match[j]=x;
return true;
}
}
}
return false;
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d%d",&n1,&n2,&m);
for(int i=0;i<m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
}
int res=0;
for(int i=1;i<=n1;i++)
{
memset(st,false,sizeof st);
if(find(i))res++;
}
printf("%d\n",res);
return 0;
}
二分图试炼之棋盘覆盖
描述
给定一个 N 行 N 列的棋盘,已知某些格子禁止放置。
求最多能往棋盘上放多少块的长度为 2、宽度为 1 的骨牌,骨牌的边界与格线重合(骨牌占用两个格子),并且任意两张骨牌都不重叠。
输入
8 0
第一行包含两个整数 N 和 t,其中 t 为禁止放置的格子的数量。
接下来t行每行包含两个整数 x 和 y,表示位于第 x 行第 y 列的格子禁止放置,行列数从 1 开始。
输出
32
输出一个整数,表示结果。
数据范围
1≤N≤100, 0≤t≤100
思路:
每一个骨牌所遮住的两个棋盘方格的横纵坐标之和一定一个是奇数,一个是偶数。不可能出现两个都是奇数或两个都是偶数。
所以转化为二分图寻找最大匹配数的问题(匈牙利算法)。
代码实现:
#include<iostream>
#include<algorithm>
#include<vector>
#include<string.h>
using namespace std;
#define N 110
int n;
bool graph[N][N],st[N][N];
pair<int,int> match[N][N];
//记录当前节点匹配的节点
int dx[4]={0,-1,0,1};
int dy[4]={-1,0,1,0};
bool findPath(int i,int j)
{//匈牙利算法
for(int k=0;k<4;k++)
{
int a=i+dx[k],b=j+dy[k];
if(a<=n&&a>=1&&b<=n&&b>=1)
{
if(st[a][b]||graph[a][b])continue;
st[a][b]=true;//标记当前节点已被访问
pair<int,int> t=match[a][b];//获取当前节点的匹配访问
if(t.first==0||findPath(t.first,t.second))
{//若匹配节点为空或可以找到增广路径
match[a][b]={i,j};//更新匹配节点
return true;
}
}
}
return false;
}
int main()
{
int t;
cin>>n>>t;
while(t--)
{
int x,y;
cin>>x>>y;
graph[x][y]=true;
}
int res=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(graph[i][j])continue;
else if((i+j)%2==1)
{//一块骨牌一定会覆盖一个奇数点和一个偶数点
memset(st,0,sizeof st);
if(findPath(i,j))res++;
}
}
}
cout<<res<<endl;
return 0;
}