匈牙利算法
bool dfs(int u)// 现在增广u这个点
{
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;// 枚举每一条与u连着的v
if(!use[v])// v在此次增广中还没有使用过(如果v在此次增广中已经试过一次了就不用在ask他了)
{
use[v]=true;
if(!link[v]||dfs(link[v]))// 如果v没有匹配,或者v有匹配但他的匹配可以匹配别的把v空出来
{
link[v]=u;
return true;
}
}
}
return false;
}
int main()
{
int ans=0;
for(int i=1;i<=n;i++)
{
memset(use,0,sizeof(use));//注意!
if(dfs(i)) ans++;
}
}
二分图基础题
luogu2071座位安排
https://www.luogu.org/problemnew/show/P2071
题目描述
已知车上有N排座位,有N*2个人参加省赛,每排座位只能坐两人,且每个人都有自己想坐的排数,问最多使多少人坐到自己想坐的位置。
输入输出格式
输入格式:
第一行,一个正整数N。
第二行至第N*2+1行,每行两个正整数Si1,Si2,为每个人想坐的排数。
输出格式:一个非负整数,为最多使得多少人满意。
输入输出样例
输入样例#1:
4
1 2
1 3
1 2
1 3
1 3
2 4
1 3
2 3
输出样例#1:
7
思路
- 因为每排座位最多坐两个人,所以一排座位对应着两个点,如果一个人想坐在这个座位,就往这个座位对应的两个点连边
- 然后匈牙利算法
- 注意!需要写邻接表
const int M=1e6+100;
const int N=1e5+10;
int link[4010];
bool use[4010];
int n;
struct node
{
int v,nxt;
}edge[M];
int head[N],cnt;
void add(int u,int v)
{
cnt++;
edge[cnt].v=v;
edge[cnt].nxt=head[u];
head[u]=cnt;
}
bool dfs(int x)
{
for(int i=head[x];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(!use[v])
{
use[v]=true;
if(!link[v]||dfs(link[v]))
{
link[v]=x;
return true;
}
}
}
return false;
}
int main()
{
cin>>n;
int a,b;
for(int i=1;i<=n*2;i++)
{
cin>>a>>b;
add(i,a);
add(i,a+n);
add(i,b);
add(i,b+n);
}
int ans=0;
for(int i=1;i<=n*2;i++)
{
memset(use,0,sizeof(use));
if(dfs(i)) ans++;
}
cout<<ans<<endl;
return 0;
}
二分图判定
- 方法黑白染色!
Hall 定理
二分图匹配
1. 几个基本概念
最小点覆盖:选取最少的点,使任意一条边至少有一个端点被选择
最大独立集:选取最多的点,使任意所选两点均不相连
定理1:最大匹配数 = 最小点覆盖
定理2:最大独立集= 顶点数 - 最大匹配数
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read() {
int x=0,f=1;char ch=' ';
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int N=1e3+10;
int n,m;
int g[N][N],link[N]//与右边点匹配的左边点;
bool use[N];// 表示在此次寻找增广路时,右边的点访没访问过
bool dfs(int x)
{
for(int i=1;i<=m;i++)
{
if(!use[i]&&g[x][i])
{
use[i]=true;
if(!link[i]||dfs(link[i]))
{
link[i]=x;
return true;
}
}
}
return false;
}
int main()
{
int e;
n=read();m=read();e=read();
int i,j;
for(i=1;i<=e;i++)
{
int u,v;
u=read();v=read();
//if(v>m||u>n) continue;
g[u][v]=true;
}
int ans=0;
memset(link,0,sizeof(link));
for(i=1;i<=n;i++)
{
memset(use,0,sizeof(use));
if(dfs(i)) ans++;
}
cout<<ans<<endl;
return 0;
}
例题
1) bzoj1741: [Usaco2005 nov]Asteroids 穿越小行星群
贝茜想驾驶她的飞船穿过危险的小行星群.小行星群是一个NxN的网格(1≤N≤500),在网格内有K个小行星(1≤K≤10000). 幸运地是贝茜有一个很强大的武器,一次可以消除所有在一行或一列中的小行星,这种武器很贵,所以她希望尽量地少用.给出所有的小行星的位置,算出贝茜最少需要多少次射击就能消除所有的小行星.
第1行:两个整数N和K,用一个空格隔开.
第2行至K+1行:每一行有两个空格隔开的整数R,C(1≤R,C≤N),分别表示小行星所在的行和列.
一个整数表示贝茜需要的最少射击次数,可以消除所有的小行星
in
3 4
1 1
1 3
2 2
3 2
out
2
思路
- 摧毁一个小行星,既可以从行也可以从列。但是行和列中必须选一个摧毁
- 所以我们从行连一条边到列,发现这个二分图的摧毁最小覆盖集上的点就可以摧毁所有小行星,又最小覆盖集=最大匹配数。
- 所以建图跑二分图匹配
code
注意!!! link有可能是关键字!!!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<cstdlib>
#include<ctime>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
inline int read(){
char ch=' ';int f=1;int x=0;
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int N=510;
int link[N],use[N];
int n,m;
int g[N][N];
bool dfs(int x)
{
for(int i=1;i<=n;i++)
{
if(!use[i]&&g[x][i])
{
use[i]=true;
if(!link[i]||dfs(link[i]))
{
link[i]=x;
return true;
}
}
}
return false;
}
int main()
{
n=read();m=read();
int i,j;
for(i=1;i<=m;i++)
{
int a=read(),b=read();
g[a][b]=1;
}
int ans=0;
for(i=1;i<=n;i++)
{
memset(use,0,sizeof(use));
if(dfs(i)) ans++;
}
cout<<ans<<endl;
return 0;
}