给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2 个点(编号 1∼n2),二分图共包含 m条边。
数据保证任意一条边的两个端点都不可能在同一部分中。
请你求出二分图的最大匹配数。
二分图的匹配:给定一个二分图 G,在 G 的一个子图 MM 中,MM 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。
匈牙利算法O(nm)
代码:
//匈牙利算法O(nm)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=510,M=1e5+10;
int h[N],e[M],ne[M],idx;
int match[N];//表示右半部点的匹配
bool vis[N];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool find(int x)
{
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(!vis[j])
{
vis[j]=true;
if(!match[j]||find(match[j]))//如果j点未匹配或j的匹配点可以找到新的点匹配
{
match[j]=x;//j匹配x
return true;
}
}
}
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n1,n2,m;
cin>>n1>>n2>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
}
int res=0;
for(int i=1;i<=n1;i++)
{
memset(vis,0,sizeof vis);
if(find(i)) res++;
}
cout<<res;
return 0;
}
373. 車的放置:
给定一个 N 行 M 列的棋盘,已知某些格子禁止放置。
问棋盘上最多能放多少个不能互相攻击的車。
車放在格子里,攻击范围与中国象棋的“車”一致。
输入格式
第一行包含三个整数 N,M,T,其中 T 表示禁止放置的格子的数量。
接下来 TT 行每行包含两个整数 x 和y,表示位于第 x 行第 y 列的格子禁止放置,行列数从 1 开始。
输出格式
输出一个整数,表示结果。
数据范围
1≤N,M≤200
思路:
考虑二分图
将放置棋子的横坐标当作左半图,纵左边当作右半图,車当作连接的边。由于二分图匹配的性质可保证匹配的点不在同行或同列。即可转换为求二分图的最大匹配
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=210,M=4e4+10;
int n,m,t;
int a[N][N],match[N]; //a[i][j]==0表示i,j之间可连边
bool vis[N];
bool find(int x)
{
for(int i=1;i<=m;i++)
{
if(!vis[i]&&!a[x][i])
{
vis[i]=true;
if(match[i]==0||find(match[i]))
{
match[i]=x;
return true;
}
}
}
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin>>n>>m>>t;
while(t--)
{
int x,y;
cin>>x>>y;
a[x][y]=1;
}
int res=0;
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof vis);
if(find(i)) res++;
}
cout<<res;
}
372. 棋盘覆盖
给定一个 N 行 N 列的棋盘,已知某些格子禁止放置。
求最多能往棋盘上放多少块的长度为 2、宽度为 1 的骨牌,骨牌的边界与格线重合(骨牌占用两个格子),并且任意两张骨牌都不重叠。
输入格式
第一行包含两个整数 N 和 t,其中 t 为禁止放置的格子的数量。
接下来 t行每行包含两个整数 x 和 y,表示位于第 x 行第 y 列的格子禁止放置,行列数从 1 开始。
思路:
和前一题类似,都是放置问题。
考虑如何将其分为二分图,将第一个点放在左半图,则其相邻的点将置与右半图。将整个图划分别的话,坐标之和为奇数的在左半图,否则在右半图。可以发现划分后每个匹配即为骨牌放置的位置,并且不会出现重叠的情况。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=110;
int m[N][N];
PII match[N][N];//存匹配的点对
bool vis[N][N];
int n,t;
int nx[]={1,0,-1,0},ny[]={0,1,0,-1};
bool find(int x,int y)
{
for(int i=0;i<4;i++) //枚举相邻点
{
int xx=x+nx[i],yy=y+ny[i];
if(!vis[xx][yy]&&!m[xx][yy]&&xx&&yy&&xx<=n&&yy<=n)
{
PII tt=match[xx][yy];
vis[xx][yy]=true;
if(!tt.first||find(tt.first,tt.second))
{
match[xx][yy]={x,y};
return true;
}
}
}
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin>>n>>t;
while(t--)
{
int a,b;
cin>>a>>b;
m[a][b]=1;
}
int res=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if((i+j)%2&&!m[i][j]) //枚举可放置奇数点
{
memset(vis,0,sizeof vis);
if(find(i,j)) res++;
}
}
cout<<res;
return 0;
}
总结:
由两个例题观之,对于求放置数量问题,可将其转化为匹配问题,再考虑如何将其划分为二分图,最后用匈牙利算法求解