首先你需要先学会二分图最大匹配,匈牙利算法或 dinic 算法均可。然后就可以看下文了。
一:二分图最大匹配模版
没啥好说的,就一个模版,让大家先复习一下。
贴上我的匈牙利算法板子。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
int n1,n2,m,head[N],tot,vis[N],match[N];
struct node{
int to,nxt;
}edge[N];
void add(int x,int y){
edge[++tot].to=y;
edge[tot].nxt=head[x];
head[x]=tot;
}
bool dfs(int x){
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if(!vis[y]){
vis[y]=1;
if(!match[y]||dfs(match[y])){
match[y]=x;
return true;
}
}
}
return false;
}
int main(){
n1=read(),n2=read(),m=read();
for(int i=1;i<=m;i++){
int x=read(),y=read();
add(x,y+n1);
}
int ans=0;
for(int i=1;i<=n1;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ans++;
}
cout<<ans<<endl;
return 0;
}
二:座位安排
与这道题有异曲同工之处。
先看题,发现将人和座位分别看成左部点和右部点,就是让我们求一个最大匹配。但是一排座位可以匹配两个人,对此如果我们仍然想用匈牙利算法的话,就可以把每个座位视为两个点,然后一个人想坐两个座位,就连出四条边。这样就完美解决此题了。
如果想用最大流来做,就可以将每个座位连向汇点的流量设为 2 2 2。这样也是可以的。
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
int n,head[N],tot,vis[N],match[N];
struct node{
int to,nxt;
}edge[N*2];
void add(int x,int y){
edge[++tot].to=y;
edge[tot].nxt=head[x];
head[x]=tot;
}
bool dfs(int x){
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if(!vis[y]){
vis[y]=1;
if(!match[y]||dfs(match[y])){
match[y]=x;
return true;
}
}
}
return false;
}
int main(){
n=read();
for(int i=1;i<=2*n;i++){
int x=read(),y=read();
add(i,2*n+2*x-1),add(i,2*n+2*x);
add(i,2*n+2*y-1),add(i,2*n+2*y);
}
int ans=0;
for(int i=1;i<=2*n;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ans++;
}
cout<<ans<<endl;
return 0;
}
三:连续攻击游戏
也是一道经典题目。
这道题乍一看没啥思路,可能会往贪心那乱想,不过再想一想,一种装备只能用一次,如果我们分别将属性和装备视作点的话,这不依然是一个二分图最大匹配问题。
由此,我们将属性向装备连边,然后进行匈牙利算法,从 1 1 1 跑到 V V V。( V V V 是值域范围,即 1 1 1 ~ 10000 10000 10000)。若发现那次找不到匹配的点,就 break,然后就满足题目中说的连续攻击的限制了。
不过此题每次 memse t初始化会超时,我们可以采用时间戳优化 v i s vis vis 数组。
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
int n,head[N],tot,vis[N],match[N],id=1;
struct node{
int to,nxt;
}edge[N];
void add(int x,int y){
edge[++tot].to=y;
edge[tot].nxt=head[x];
head[x]=tot;
}
bool dfs(int x){
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if(vis[y]-id){
vis[y]=id;
if(!match[y]||dfs(match[y])){
match[y]=x;
return true;
}
}
}
return false;
}
int main(){
n=read();
for(int i=1;i<=n;i++){
int x=read(),y=read();
add(x,i+10000),add(y,i+10000);
}
int ans=0;
for(int i=1;i<=10000;i++,id++){
if(dfs(i)) ans++;
else break;
}
cout<<ans<<endl;
return 0;
}
四:矩阵游戏
非常巧的题。
首先行交换和列交换,我们只需进行其一即可。
考虑把点视为边的话,实际上是每行每列都有匹配。对于两个黑色数字,如果它们初始不在同一行(列)的话,那么进行操作后,它们依然不在同一行(列)。所以对于
(
i
,
j
)
(i,j)
(i,j),我们可以连边
i
−
>
j
i->j
i−>j。然后对每一行开始跑二分图最大匹配,看答案是否为
n
n
n。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
int T,n,head[N],tot,vis[N],match[N];
struct node{
int to,nxt;
}edge[N];
void add(int x,int y){
edge[++tot].to=y;
edge[tot].nxt=head[x];
head[x]=tot;
}
bool dfs(int x){
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if(!vis[y]){
vis[y]=1;
if(!match[y]||dfs(match[y])){
match[y]=x;
return true;
}
}
}
return false;
}
int main(){
T=read();
while(T--){
memset(head,0,sizeof(head)),tot=0;
memset(vis,0,sizeof(vis)),memset(match,0,sizeof(match));
n=read();
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int x=read();
if(x) add(i,j+n);
}
}
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ans++;
}
if(ans>=n) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}