二分图定义:
可以所有点划分到两边去,使得所有的边都是在集合之间的,
且在左右两个集合中不存在其他边
性质:
一定不含有奇数环,可能包含长度为偶数的环,不一定是连通图
if 是二分图的话,一定不含奇数环
if 是一个图含有奇数环打得话,一定不是二分图
性质证明(图解):
性质应用:
1.AcWing 860. 染色法判定二分图 - AcWing
思路:
奇数环:环中边的数量是奇数
分组(染色) - - 可以用 1/2 表示不同颜色(组),0表示未染色(分组)
//由于某个点染色成功不代表整个 图就是二分图,
只有当某一个点染色失败的时候才能理科break /return
染色失败代表相邻两个点染上了不同的颜色
这里的染色法可以用dfs遍历所有边也可以dfs,代码如下:
bool dfs(int u, int col) {
st[u] = col;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (!st[j]) {
if (!dfs(j, 3 - col))return false;
}
else if (st[j] == col)return false;
}
return true;
}
bool bfs(int u){
int hh=0,tt=0;
q[0]={u,1};
st[u]=1;
while(hh<=tt){
PII t=q[hh++];
int u=t.first,c=t.second;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!st[j]){
st[j]=3-c;
q[++tt]={j,3-c};
}else if(st[j]==c)return false;
}
}
return true;
}
那么当 limit∈[ans,109]limit∈[ans,109] 时,所有边权大于 limitlimit 的边,必然是所有边权大于 AnsAns 的边的子集,因此由此构成的新图也是二分图。
当 limit∈[0,ans−1]limit∈[0,ans−1] 时,由于 ansans 是新图可以构成二分图的最小值,因此由大于 limitlimit 的边构成的新图一定不是二分图。
//思路: 二分 +染色法判断二分图,枚举每一种可能结果,找到临界点即为所求
关押罪犯:
题意: n个罪犯里面有m条仇恨值关系,把n个罪犯分到不同的
两个监狱中,使得他们之间的仇恨值尽可能小,
要你求出一个监狱里面的仇恨值之和
题意抽象:
将罪犯当做点,罪犯之间的仇恨当做点与点之间无向边,
边的权重就是点之间的仇恨值
原题变成:
将所有点分成两组,就两组中的权重最大值的尽可能小
我们可以在[0,1e9]之间枚举最大 边权 lim
然后把所有> lim的边都放到 组间,把所有<=lim的边都放到组内
然后判断这样的一个图是否是二分图(这里我们可以使用染色法判断)
同时由于1e9 过大了,我们考虑二分这个区间,加速寻找ans
证明(mid<ans)>mid 的图是> ans的子集
3.AcWing 861. 二分图的最大匹配 - AcWing (匈牙利板子)
匹配:在图论中,一个「匹配」是一个边的集合,其中任意两条边都没有公共顶点。
最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。
完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。
交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。
增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替 路称为增广路,如下。
代码模版:
bool find(int u) {
// 遍历所有她喜欢的女孩:
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (!st[j]) { //if在这轮匹配中女孩未被预定
st[j] = true;//那x就预定这个女孩
//如果这个女孩没有npy 或者他的这个男朋友可以换个女朋友
//那么x就有机会啦!!!
if (!match[j] || find(match[j])) {
match[j] = u;
return true;
}
}
}
//可怜的单身狗,要光棍一辈子咯
return false;
}
本题难点在于如何抽象出模型,即怎么把矩形中的点分成相邻两种不同的点
原题意:
矩形中最多放多少栈2*1的卡片,使得卡片之间不重叠
抽象题意:
把一个点和他相邻的点(格子)连上一条边,问:
最多选出来有多少 条边,使得选出的边时没有公共点的
-- 最大匹配 --匈牙利解决
但是使用匈牙利打得前提是二分图
那么我们这样染色:
对于一个格子坐标 x,y x+y 是奇数的放一块,是偶数的放一块
这样我的二分图就得到了
//这里的find 函数代码由一维改为二维而已,思路试试一样的
bool find(int x,int y) {
for (int i = 0; i < 4; ++i)
{
int a = x + dx[i], b = y + dy[i];
if (a<1 || a>n || b<1 || b>n)continue;
if (st[a][b] || g[a][b])continue;
st[a][b] = true;
PII t = match[a][b];
if (t.x == 0 || find(t.x, t.y)) {
match[a][b] = {x,y};
return true;
}
}
return false;
}
匈牙利 求解:
最小点覆盖 、最大独立集 、最小路径点覆盖(最小路径重复点覆盖)
最大匹配数= 最小点覆盖 =总点数 -最大独立集 = 总点数-最小路径点覆盖
本题主要考察二分图性质: 二分图中 最少点覆盖 == 最大匹配数
题意:
对于灭国任务有 a,b 两台机器可以完成,但是每个任务i a需要在a[i]模式上完成,
b需要在b[i]模式上完成,换模式需要重启,求最少重启次数
抽象题意:
开始 a,b都是0 不需要代价,把每个任务抽象一条边,做一个任务(只需要经过边上两个端点之一即可) == 选择边上的某个点:
那么问题就转换为在剩下的 n+m-2中最少选择几个点可以把,所有边全部覆盖住--最少点覆盖
而二分图中 最少点覆盖 == 最大匹配数
so 问题得解
题意: 问棋盘上最多能放多少个不能互相攻击的骑士(国际象棋的“骑士”,类似于中国象棋的“马”,按照“日”字攻击,但没有中国象棋“别马腿”的规则)。
走法图解:
so 我们有发现能走的点是 i+j 为奇数的 白格子,那么有变成二分图的问题了--
抽象题意:
最多可以选出多少个点使得 这些点之间是不能相互攻击到的
等价于,选出最多的点使得这些点之间没有边 --最大独立集
最大独立集 定义:
选出最多的点,使得选出的点之间(内部)没有边
最大团:--二者互补
选出最多的点,使得选出的点之间(内部)有边
//原图的最大独立集 和补图的最大团相等
关联:
在二分图中求最大独立集 ---
选出最多的点使得点之间无边
== 去掉最少的点,把所有边破坏掉
==求最小点覆盖 == 求最大匹配
结论:
1.最少路径点覆盖=总点数- 最大匹配
2.拓展:
最小路径重复点覆盖:在最小路径覆盖问题的基础上,去掉互不相交。
结论:记原图G,求传递闭包后的图G’,则G的最小路径重复点覆盖=G’的最小路径覆盖
//求传递闭包
for (int k = 1; k <= n; ++k)
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
g[i][j] |= g[i][k] & g[k][j];
在原来的基础上多求一个传递闭包 即可求出最短路径重复点覆盖