我用自己的语言解释一下何为二分图:图中所有点分成两部分,就像站队,左边一队,右边一队。同一队两点之间没有关系,不同队的可能存在多种关系。

  二分图的最大匹配:比如相亲,左边为男生队,右边为女生队,男女存在多种关系(比如男1喜欢女2和女3),男男之间当然没有关系了。最大匹配就是这么个情况,尽量成全更多的男女成为一对,最多的对数,就是最大匹配数。

  匈牙利算法:关键点是找增广路,是这么个情况:男1喜欢女1,但是女1已经和男4好上了,男1很执着,女1思想也不太坚定,于是女1问男4,他可不可以换个女生。如果男4成功换了女伴,那么理所当然男1成功和女1成为情侣!这就是找到了一条增广路

  下面拿板子题来说明代码:  ​​HDU 2063 ​

题意是中文就不多做解释了

  

#include<iostream>   
#include<cstdio>    
#include<cstring>
const int maxn=505;
int e[maxn][maxn];
int book[maxn];
int match[maxn];
int k,n,m;
using namespace std;
int dfs(int x)
{
for(int i=1;i<=m;i++)
{
if(!book[i]&&e[x][i])  //关于book数组,我这么解释:1:第2层if没有发生,即男x无法和i配对,说明女i已经有人了,而且女i的男友无法再找到新女生,此时有必要把book[i]=1,因为这个女人碰不得啊,以后大家就不要再找她了。
                   //2:第二层if 发生了,女i被标记,因为女i的男友想再找其他女生,就不能再找女i了。
{
book[i]=1;
if(!match[i]||dfs(match[i]))//女i没有男友或者男友可以换个女生,那么x可以和i配对

{
match[i]=x;
return 1;
}
}
}
return 0;
}
int main()
{

while(scanf("%d",&k))
{
if(k==0)
break;
int ans=0;
scanf("%d%d",&n,&m);
memset(match,0,sizeof(match));
memset(e,0,sizeof(e));
int a,b;
for(int i=1;i<=k;i++)
{
scanf("%d%d",&a,&b);
e[a][b]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)    //每次清空book
{  
book[j]=0;
}
if(dfs(i))   //
ans++;
}
cout<<ans<<endl;
}
return 0;
}

    二分图最大独立集:实质是个点集,这个集合里的点无论怎样都两两相连不到一起,满足这个要求的点数最多的一个。也就是集合里任意两点之间无关系。用最大匹配求出最大独立集,

    最大独立集 = 顶点数 - 最大匹配数

    ​​HDU1068 ​​     

    题意:n个同学,一些男女同学会有缘分成为情侣,格式ni:(m) n1 n2 n3表示同学ni有缘与n1,n2,n3成为情侣,求集合中不存在有缘成为情侣的同学的最大同学数

    模板,但是要注意,这并不是两个集合,所以最大匹配相当于求了两遍,最大匹配 / 2;

    还有,输入格式注意一下,第一次这么搞,没想到scanf还有这种操作。

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=1001;
int e[maxn][maxn];
int match[maxn];
int book[maxn];
int n;
int sum=0;
void init()
{
memset(e,0,sizeof(e));
int st,m;
sum=0;
memset(match,-1,sizeof(match));
for(int i=0;i<n;i++)
{
scanf("%d: (%d)",&st,&m);
int to;
while(m--)
{
scanf("%d",&to);
e[st][to]=1;
}
}
}
int dfs(int x)
{
for(int i=0;i<n;i++)
{
if(book[i]==0&&e[x][i]==1)
{
book[i]=1;
if(match[i]==-1||dfs(match[i]))
{
match[i]=x;
return 1;
}
}
}
return 0;
}
int main()
{
while(~scanf("%d",&n))
{
init();
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
book[j]=0;
if(dfs(i))
sum++;
}
printf("%d\n",n-sum/2);
}
return 0;
}

  下面放个很有意思的题  ​​HDU1281​

  中文题意,为了防止车子互相攻击,那么车在某一行+某一列只有一个。相当于行与列的匹配,由于存在不能放车的点,用e[][]来存坐标来进行判断能否放车,其实就和之前的一样,e[i][j]为第i行和

第j列没有关系,仅此而已。所谓的重要点,就是去掉它以后,求出的最大匹配数比原来的少,求这些点。那么我们枚举每一个e[][]==1大的点,去掉它,跑一下匈牙利,求出的最大匹配和原来的比较。计数。

  

1 #include<iostream>
2 #include<cstring>
3 #include<cstdio>
4 using namespace std;
5 const int maxn=105;
6 int e[maxn][maxn];
7 int match[maxn];
8 int book[maxn];
9 int n,m,k;
10 int all=0;
11 void init()
12 {
13 memset(e,0,sizeof(e));
14 all=0;
15 }
16 int dfs(int x)
17 {
18 for(int i=1;i<=m;i++)
19 {
20 if(!book[i]&&e[x][i])
21 {
22 book[i]=1;
23 if(!match[i]||dfs(match[i]))
24 {
25 match[i]=x;
26 return 1;
27 }
28 }
29 }
30 return 0;
31 }
32 int xyl()
33 {
34 int ans=0;
35 memset(match,0,sizeof(match));  //注意,每次跑匈牙利都要初始化match!
36 for(int i=1;i<=n;i++)
37 {
38 memset(book,0,sizeof(book));
39 if(dfs(i))
40 ans++;
41 }
42 return ans;
43 }
44 int main()
45 {
46 int ww=1;
47 while(~scanf("%d%d%d",&n,&m,&k))
48 {
49 init();
50 int x,y;
51 int cnt=0;
52 for(int i=1;i<=k;i++)
53 {
54 scanf("%d%d",&x,&y);
55 e[x][y]=1;
56 }
57 all=xyl();
58 for(int i=1;i<=n;i++)
59 {
60 for(int j=1;j<=m;j++)
61 {
62 if(e[i][j])
63 {
64 e[i][j]=0;
65 int ans=xyl();
66 // cout<<ans<<endl;
67 if(ans<all)
68 cnt++;
69 e[i][j]=1;
70 }
71 }
72 }
73 printf("Board %d have %d important blanks for %d chessmen.\n",ww++,cnt,all);
74 }
75

    二分图的染色法判断: 1. 二分图当且仅当不含奇数环。(手推一下,会出现矛盾,所以不能含有奇数环),不含奇数环一定是二分图。

    具体参见ACWING 图论第三讲。对每一个点进行染色,比如某点染1色,那么其相邻点染2色,如果在染的过程中,出现了矛盾,比如i 点本来改染成1色,但是它的当前配色为2,那肯定不是个二分图。​​ACWING 基本板子题​​:这里用邻接表来存图

    

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=2e5+10;
int n,m;
int h[maxn],e[maxn],ne[maxn];  //对e,ne要开大点,我也不知道为撒
int color[maxn];
int ok=0;
int idx;
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
bool dfs(int u,int c)
{
color[u]=c;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(!color[j])
{
if(!dfs(j,3-c))
{
return false;
}
}
else if(color[j]==c)
{
return false;
}
}
return true;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof(h));
for(int i=1 ;i<=m ;i++)
{
int a,b ;
scanf("%d%d",&a ,&b );
add(a,b);
add(b,a);
}
for(int i=1 ; i <= n; i++)
{
if(!color[i])
{
if(!dfs(i,1))
{

ok=1;  //出现矛盾
break;
}
}
}
if(ok)
cout<<"No"<<endl;
else
cout<<"Yes"<<endl;
}

         

   ​​HDU 5285 ​

    原题自行打开看吧,我解释一下题意:就是n个人,分成两组,m个不认识关系。把认识的人分一组,分两组。比如输入1 2,就表明1与2不认识,不认识就不能分到同一组。

    很明显,判断二分图,不是二分图就输出Poor wyh。是的话先输出大的一组,然后是较小的一组。套基本二分图染色模板,但是要注意,题没这么简单,因为题目中有多个连通块,就是有多个二分图,其次还存在单点。所以呢,运用贪心的思想,分别把单个的二分图求出来,然后用ans加上较小的那一组的人数,这样单点就自动记入较大的一组,n减ans就可以了。因为不同的二分图:比如A组和B组,A组的人肯定和B组不存在不认识关系,所以他们可以加入同一组。很多题解加入了vis等好多不易理解且繁琐的东西,我依据二分图染色模板,改造出了易于小白理解的代码,与原模板没什么两样。

    

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=2e5+10;
int h[maxn],e[maxn],ne[maxn];  //邻接表存图
int num[3];
int color[maxn];
bool vis[maxn];
int idx;
int ok=0;
void init()
{
idx=0;ok=0;
memset(h,-1,sizeof(h));
memset(e,0,sizeof(e));
memset(ne,0,sizeof(ne));
// memset(vis,false,sizeof(vis));
memset(color,0,sizeof(color));
}

void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
bool dfs(int u,int c)
{
// vis[u]=true;
color[u]=c;
num[c]++;  //计数
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(!color[j])
{
if(!dfs(j,3-c))
return false;
}
else if(color[j]==c)
return false;

}
return true;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
init();
int n,m;
scanf("%d%d",&n,&m);
int a,b;
int mid=m;
while(mid--)
{
scanf("%d%d",&a,&b);
add(a,b);add(b,a);
}
// cout<<m<<endl;

if(n<=1)    //坑点,记得特判!
{
printf("Poor wyh\n");
continue;
}
if(m==0)
{
printf("%d 1\n",n-1);
continue;
}
int ans=0;
for(int i=1;i<=n;i++)
{
if(!color[i])
{
num[1]=0;num[2]=0;  //新的二分图,那么计数器就要清一下
if(!dfs(i,1))
{
ok=1;break;
}
ans+=min(num[1],num[2]);
}
}
if(ok)
printf("Poor wyh\n");
else
{
printf("%d %d\n",n-ans,ans);
}
}
}