DAY 8
1.AcWing 860. 染色法判定二分图
考察点:染色法 二分图 深度优先遍历DFS来进行染色 利用邻接表来存储图
基础知识(重要性质):
染色法:
将所有点分成两个集合,使得所有边只出现在集合之间,就是二分图
二分图:一定不含有奇数环,可能包含长度为偶数的环, 不一定是连通图
(1)图论中重要性质:一个图是二分图当且仅当图中不含有奇数环(环当中的边数是奇数)
(2)二分图定义:集合之间没有边相连,利用染色法判断是否出现矛盾(利用DFS来判断是否有矛盾)二分图如图二所示
(3)染色时一条边的两点需要属于不同颜色
dfs的应用
代码思路:
染色可以使用1和2区分不同颜色,用0表示未染色
遍历所有点,每次将未染色的点进行dfs, 默认染成1或者2
由于某个点染色成功不代表整个图就是二分图,因此只有某个点染色失败才能立刻break/return
染色失败相当于存在相邻的2个点染了相同的颜色
最终实现代码:
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010,M = 200010;//因为是存储无向边,两点之间有两条边
int n,m;
int h[N],e[M],ne[M],idx;
int color[N];
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])//相邻的点没有颜色,则递归处理这个相邻点
{
dfs(j,3-c);
if(!dfs(j,3-c)) return false;//(3 - 1 = 2, 如果 u 的颜色是2,则和 u 相邻的染成 1)
//(3 - 2 = 1, 如果 u 的颜色是1,则和 u 相邻的染成 2),如果染色没成功则返回
}
else if(color[j] == c) return false;//如果已经染色,判断颜色是否为 3 - c
}
return true;//如果不是,说明冲突,返回
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);//初始化邻接表
while(m--)//建边
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b),add(b,a);
}
bool flag = true;
for(int i = 1;i<=n;i++)//遍历点
{
if(!color[i])
{
if(!dfs(i,1))//染色第一个点,并递归处理和它相邻的点
{
flag = false;
break;
}
}
}
if(flag) puts("Yes");
else puts("No");
return 0;
}
2.AcWing 861. 二分图的最大匹配
考察点:匈牙利算法 O(nm)
匈牙利算法:
用通俗的语言来说,匈牙利算法其实就是扮演一个月老的身份,前提是这个月老的三观非常正确,坚持一贯的一个男生搭配一个女生的恋爱观,在这个过程中,如何最快的最大程度的匹配出几对情侣就是匈牙利算法所要做的事情。(俗称牛头人算法 哈哈哈 开个玩笑)
st数组:假设左一和右一已经是一对,左二也看上了右一。
如果每次不更新st为false,左二发现match[1]=1,然后左一再去find(1),左一发现match[1]=1,虽然就是他自己,但是他还是又去find(match[1]),所以就会一直递归下去。
如果每次把st更新为 false,左二每次考虑一个妹子再更新为true,那么左一在find(1)的时候就会知道,这个妹子已经被左二考虑过了,下次可以不用再考虑这个妹子了。
st数组才是匈牙利算法的精华。st数组就保证了,只有当左一还有备胎的时候,才会把右一让给别人;同时也防止无限递归。还有一种理解:如果你每次不把st重新置为false,那剩下的人一看到前面的妹子st已经为true,不去让妹子的对象换掉这个妹子,直接就放弃了,会影响最后结果。
最终实现代码:
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510,M = 100010;
int n1,n2,m;//n1,n2分别是两个点集的点的个数
int h[N],e[M],ne[M],idx;//邻接表写法,存稀疏图
int match[N];//match[j]=a,表示女孩j的现有配对男友是a
bool st[N];//st[]数组我称为临时预定数组,匈牙利算法中的精华
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
//这个函数的作用是用来判断,如果加入x来参与模拟配对,会不会使匹配数增多
bool find(int x)
{
for (int i = h[x]; i != -1; i = ne[i])
{
int j = e[i];//映射到图中的编号进行遍历
if(!st[j])//心仪的女生还没有被自己预定
{
st[j] = true;
//如果女孩j没有男朋友,或者她原来的男朋友能够预定其它喜欢的女孩。配对成功,更新match
if(match[j]== 0||find(match[j]))//核心步骤
{
match[j] = x;
return true;
}
}
}
return false;
}
int main()
{
scanf("%d%d%d",&n1,&n2,&m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);//存边只存一边就行了,虽然是无向图。
}
int res = 0;//记录最大匹配
for(int i = 1;i<=n1;i++)
{
memset(st, false, sizeof st);//每轮重置st是为了每次find搜索时避免重复搜索,这个是st的作用。重置是因为对于之前的一个点它相对于他走不通,但对于当前的点并不一定,所以要在重试一下这个点对于当前的点是否可行。 匈牙利算法精华所在
if (find(i)) res ++ ;
}
printf("%d\n",res);
return 0;
}