二分图
1. 二分图原理
原理
1. 二分图定义
- 二分图又称为二部图,如果一个点集可以被分成两个部分,所有的边都在这两个部分之间,而每个集合内部没有边,则称这个图是一个二分图。如下图:
-
显然(哈哈),如果一个图是二分图,则其去掉一些边之后还是二分图。
-
下面证明一个重要的定理:一个图是二分图 ⟺ \iff ⟺图中不存在奇数环 ⟺ \iff ⟺染色过程中不存在矛盾
- 首先证明:图中不存在奇数环 ⟺ \iff ⟺染色过程中不存在矛盾
首先证明充分性:图中不存在奇数环 ⇒ \Rightarrow ⇒染色过程中不存在矛盾。可以证明其逆否命题成立,即染色过程中存在矛盾,则图中存在奇数环,因为染色法存在矛盾,说明一定存在相邻的两个点颜色相同,(白、黑、白、黑、…、白)因此一定存在奇数环;
再证明必要性:图中不存在奇数环 ⇐ \Leftarrow ⇐染色过程中不存在矛盾。可以使用反证法,染色法不存在矛盾,但存在奇数环,对于某个环而言,如果是奇数环的话,根据染色过程,一定有(白、黑、白、黑、…、白),最后一个一定是白,否则不能构成奇数环,此时发现染色法矛盾了,因为白色的点和白色的点相邻了。
- 然后证明:一个图是二分图和上面两个条件等价
首先证明必要性:一个图是二分图 ⇐ \Leftarrow ⇐染色过程中不存在矛盾。染色法不存在矛盾,则可以将白点和黑点分成两个部分,此时可以构造出一个二分图,成立。
再证明充分性:一个图是二分图 ⇒ \Rightarrow ⇒图中不存在奇数环。反证法,假设最后推出图中存在奇数环,则最终图中一定存在两个相邻的点颜色相同,矛盾,因此结论成立。
2. 染色法判断二分图
- 根据上面三个结论的等价性,我们可以使用染色法判定一个图是不是二分图。
- 染色法:如果一个点被染成白色,则这个点相邻的点都应该被染成黑色,反之也是如此。根据染色法的过程,如果一个连通块中的某个点颜色确定了,则整个连通块每个点的颜色也就确定了。
- 如果在染色过程中没有出现矛盾,则是二分图;否则不是二分图。
- 代码实现中可以使用1、2分别表示两种颜色,则如果当前点颜色为c,其相邻的点的颜色应该为3-c。
代码模板
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010, M = 200010;
int n, m;
int h[N], e[M], ne[M], idx;
int color[N]; // 0代表当前节点还未染色
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 当前给u染成颜色c, 返回是否能染色成功
bool dfs(int u, int c) {
color[u] = c;
for (int i = h[u]; ~i; 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);
while (m--) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
bool success = true; // 是否染色成功
for (int i = 1; i <= n; i++)
if (!color[i]) {
if (!dfs(i, 1)) {
success = false;
break;
}
}
if (success) puts("Yes");
else puts("No");
return 0;
}
3. 匈牙利算法
- 一些概念:
- 匹配:是指中的一组边的集合,每两条边之间没有公共点。
- 最大匹配:边数最多的匹配。
- 匹配点:指在匹配当中的点。
- 非匹配点:指不在匹配当中的点。
- 增广路径:指一条路径,这条路径起始位置都是第一个集合中的非匹配点,中间经过(非匹配边,匹配边,非匹配边,…,非匹配边),到达右边一个非匹配点。
- 最大匹配 ⟺ \iff ⟺不存在增广路径,证明略。
- 匈牙利算法是针对二分图来说的,它是求二分图的一个最大匹配。
- 一个形象的例子就是男女恋爱关系,二分图两个集合,一个集合代表男生,一个集合代表女生,两个集合之间的边代表男女生之间存在好感,问最多确定多少对恋爱关系(男女生都不能脚踏多只船,即不能有选出的两条边有公共节点)。这里使用这个例子阐述匈牙利算法的过程:
(1)我们可以随便考察两个集合中的某个集合,比如我们考察男生这个集合,按照编号递增的顺序给每个男生找女朋友;
(2)考察男1号,因为女6号还没有确定恋爱关系,所以可以与女6号成功牵手,连上红线;
(3)接着考察男2号,因为女5号还没有确定恋爱关系,所以可以与女5号成功牵手,连上红线;此时确定的恋爱关系如下图:
(4)接着考察男3号,但发现女6号已经和男1号确定恋爱关系了,此时就是匈牙利算法的关键了,此时女6号就会找到她现在的男友(1号),看看他能不能换个女友,如果不能换的话,男3号接着看下一个他喜欢的女生,但是结果发现男1号可以换个女友,此时1、6分手,1号和8号确定恋爱关系,然后3、6号成为情侣,如下图(绿色代表分手了):
(5)考察男4号,因为女7号还没有确定恋爱关系,所以可以与女7号成功牵手,连上红线;如下图:
最终有四条红线,代表有四对情侣,因此最大匹配是4。
下面这个网友的评论很精彩:
-
匈牙利算法的时间复杂度最坏情况下是 O ( n × m ) O(n \times m) O(n×m)的。
-
-
C++
#include <cstring>
#include <iostream>
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]; // 存储第二个集合中的每个点 当前 匹配的第一个集合中的点是哪个
bool st[N]; // 表示第二个集合中的每个点是否已经被遍历过
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool find(int x) {
// 遍历自己喜欢的女孩
for (int i = h[x]; i != -1; i = ne[i]) {
int j = e[i];
if (!st[j]) { // 如果在这一轮模拟匹配中,这个女孩尚未被预定
st[j] = true; // 那x就预定这个女孩了
// 如果女孩j没有男朋友,或者她原来的男朋友能够预定其它喜欢的女孩。配对成功
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);
if (find(i)) res++;
}
printf("%d\n", res);
return 0;
}
4. 最小点覆盖
- 这个概念针对任意的无向图都是成立的。是指我们从图中选出最少的点集,使得所有的边中的两个端点至少有一个端点在该点集中。
- 在二分图中,最小点覆盖==最大匹配数。假设在二分图中最小点覆盖为n,最大匹配数为m,下面证明 n ≥ m n \ge m n≥m且等号可以成立。
(1)首先证明 n ≥ m n \ge m n≥m。因为对于最大匹配来说,每两条边之间没有公共点,要想把所有用点覆盖所有的边,则必须每条边都选一个端点,所以 n ≥ m n \ge m n≥m。
(2)证明等号可以成立。这里使用构造性证明。
(2.1)求二分图的最大匹配;
(2.2)从左部每个非匹配点出发,做一遍增广(一定不会成功,成功的话就可以换成增广了),标记所有经过的点(左右都需要标记)
做完上面的步骤之后,就可以构造出一个最小覆盖的方案,方案中包含的点=左部所有未被标记的点+右部所有被标记的点。如下图:
因为:方案中包含的点 = 左部所有未被标记的点 + 右部所有被标记的点。又左部所有未被标记的点一定是匹配点(逆否命题),右部所有被标记的点一定是匹配点(逆否命题),因为又有性质③,对于每条匹配边,我们必然只选其中的一个点,被标记的选择右边的点,未被标记的选择左边的点,所以n可以取到m。
根据在左部右部,以及匹配不匹配的情况,我们可以构造出四种边:
① 左边匹配,右边匹配;②左边非匹配,右边匹配;③ 左边匹配,右边非匹配;④ 左边非匹配,右边非匹配;
对于第①种情况我们已经讨论过;对于第④种情况不可能出现,否则的话我们求的不是最大匹配。
对于第②种情况,左边非匹配的点一定是被标记的点,因为走的是增广路径,走到右部的点一定是被标记的,这样的边一定被选;
对于第③种情况,这种情况不可能存在,右边非匹配一定没有被标记,因此左部的点不可能是匹配点;
- 综上所述,我们证明了 n ≥ m n \ge m n≥m且可以取到m,将所有的边覆盖住,因此 n = = m n == m n==m。
5. 最大独立集
- 这个概念针对任意的无向图都是成立的。是指我们从图中选出最多的点集,使得选出的点之间都没有边。
- 另外还有一个与最大独立集相对的概念最大团,是指我们从图中选出最多的点集,使得选出的点之间都有边。
- 原图的最大独立集就是补图的最大团,原图的最大团就是补图的最大独立集。我们只需要掌握其中一个,另一个就自然掌握了。(所谓补图是指边互补,你有的边我没有,我有的边你没有)。
- 在二分图中,最大独立集t == 总点数n - 最大匹配数m。(这里的n是指两个集合中 点个数之和)证明如下:
- 最大独立集 ⟺ \iff ⟺去掉最少的点,将所有边都破坏掉,剩余的点就是最大独立集 ⟺ \iff ⟺找最小点覆盖 ⟺ \iff ⟺找最大匹配。
- 另外提一点,最大独立集问题在一般的图上是一个NPC问题,只有在二分图上才能使用匈牙利算法。
6. 最小路径点覆盖
- 这个概念是针对DAG(有向无环图)的。最小路径点覆盖又被称为最小路径覆盖,是指在一个DAG中,我们最少需要使用多少条互不相交(点和边都不重复)的路径将所有点都覆盖住。
- 这里使用到的技巧是拆点,假设原图中有n个点,将每个点都复制一份放在右边(此时新图中有2n个点),假设点 i i i复制后的点为点 i ′ i' i′,我们将边 ( i , j ) (i, j) (i,j)变为新图中的 ( i , j ′ ) (i, j') (i,j′)。新图必然是一个二分图,在这个二分图中求最大匹配数m,则最终有:原图的最小路径点覆盖t == 原图总点数n - 新图的最大匹配数m。下面证明这个结论:
- 考虑原图中的任意一条路径转化到新图中是啥样的:
(1)每条路径转化到新图中一定对应新图的一个匹配,即每个点只会在一条边中。反之也成立。
(2)我们可以看一下原图中每条路径的终点,对应到新图中的出点是没有出边的,即左部的非匹配点,例如上图中的点3。同理左部的每个非匹配点一定也对应着原图中的路径。即使孤立点也可以看成一个终点,符合要求。
最小路径点覆盖 ⟺ \iff ⟺让左部的非匹配点最少 ⟺ \iff ⟺让左部的匹配点最多 ⟺ \iff ⟺找最大匹配。
- 综上所述,有:原图的最小路径点覆盖t == 原图总点数n - 新图的最大匹配数m。
7. 最小路径重复点覆盖
- 最小路径重复点覆盖:也是针对DAG来说的;将最小路径点覆盖中路径不能相交的条件去掉即可,即我们最少用多少条路径(点和边都可以重复)可以将所有的点覆盖住。
- 求最小路径重复点覆盖的步骤如下:
(1)求原图的传递闭包得到新图;
(2)则原图的最小路径重复点覆盖 ⟺ \iff ⟺新图的最小路径点覆盖。
-
下面对上述等价性进行证明:
① 充分性:依次考虑原图的每条符合条件的路径,当我们考察到第i条路径时,如果路径上的点和前i-1条边上的点重复,则直接跳过即可,新图中加了很多边,可以跳过。另外第i条路径上的点不可能全部和前i-1条边上的点重复,否则的话第i条路径就没有存在的必要了。
② 必要性:将新图中间接转移过去的边展开成原来的边即可得到原图中的路径。
2. AcWing上的二分图题目
AcWing 257. 关押罪犯
问题描述
-
问题链接:AcWing 257. 关押罪犯
分析
- 这一题相当于让我们求解:将图中的点分为两个集合,使得每个集合中的边权的最大值最小。
- 这一题可以使用二分来求解,二分的区间是 [ 0 , 1 0 9 ] [0, 10^9] [0,109],为什么可以使用二分来求解呢?因为答案具有二段性。
- 性质是:把边权值大于mid的边都保留,小于mid的边都删除,然后判断这个图是不是二分图。
- 对于答案右边的点,因为我们取得阈值更大了,保留的边会更少,答案都满足条件,因此答案右边的点都满足这个性质;答案左边的点都不满足这个性质,因为答案是所有符合条件中最小的一个。
代码
- C++
#include <iostream>
#include <cstring>
using namespace std;
const int N = 20010, M = 200010;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int color[N];
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool dfs(int u, int c, int mid) {
color[u] = c;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (w[i] <= mid) continue;
if (!color[j]) {
if (!dfs(j, 3 - c, mid)) return false;
} else if (color[j] == c) return false;
}
return true;
}
bool check(int mid) {
memset(color, 0, sizeof color);
for (int i = 1; i <= n; i++)
if (!color[i] && !dfs(i, 1, mid))
return false;
return true;
}
int main() {
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m--) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
int l = 0, r = 1e9;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", r);
return 0;
}
AcWing 372. 棋盘覆盖
问题描述
-
问题链接:AcWing 372. 棋盘覆盖
分析
- 我们将可以放置骨牌的格子看成点,可以放置骨牌的两个格子之间连一条边。则问题就转化为了:最多取多少条边,所有选出的边无公共点。相当于让我们求最大匹配。我们还要判断这个图是不是二分图。
- 因为:一个图是二分图 ⟺ \iff ⟺这个图可以二染色。我们需要判断棋盘是否可以二染色即可,对于棋盘,存在一种经典的二染色方式,如下图:
- 由上面分析可知,任意的棋盘都满足二染色,对应的图是二分图,所以此时就可以使用匈牙利算法求解了。
- 那么如何判断每个格子的颜色呢?我们可以给每个格子一个编号,等于横纵坐标之和,和为偶数的是一种颜色,和为奇数的是另一种颜色。我们枚举偶点或者奇点都可以。
代码
- C++
#include <iostream>
#include <cstring>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int n, m; // 边长、不能放置的点的数量
bool g[N][N]; // 为true代表有障碍物
PII match[N][N]; // 存储第二个集合中的每个点 当前 匹配的第一个集合中的点是哪个
bool st[N][N]; // 表示第二个集合中的每个点是否已经被遍历过
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
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;
}
int main() {
cin >> n >> m;
while (m--) {
int a, b;
cin >> a >> b;
g[a][b] = true;
}
int res = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if ((i + j) % 2 && !g[i][j]) {
memset(st, 0, sizeof st);
if (find(i, j)) res++;
}
cout << res << endl;
return 0;
}
AcWing 376. 机器任务
问题描述
-
问题链接:AcWing 376. 机器任务
分析
- 一个任务可以在A、B两台机器中的任意一个被完成。刚开始A、B都处于模式0,因此需要切换到0的任务不需要重启就可以完成,可以不用考虑(因为不会影响重启次数)。
- 我们的问题可以转换成:需要从A、B的N+M-2个模式当中最少选择多少个模式可以把每个任务完成,如果将 a [ i ] 、 b [ i ] a[i]、b[i] a[i]、b[i]之间连一条无向边,则把每个任务完成相当于在这条无向边上选择一个点。因此为题最终变为了:我们最少需要选择多少点可以把所有的边全部覆盖住。这是一个最小点覆盖的问题。
- 又因为这是一个二分图,所以最小点覆盖n==最大匹配数m,我们求一遍最大匹配即可。
代码
- C++
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
int n, m, k;
bool g[N][N]; // 表示两点之间是否有任务
int match[N];
bool st[N];
bool find(int x) {
for (int i = 1; i < m; i++)
if (!st[i] && g[x][i]) {
st[i] = true;
if (match[i] == -1 || find(match[i])) {
match[i] = x;
return true;
}
}
return false;
}
int main() {
while (cin >> n, n) {
cin >> m >> k;
memset(g, 0, sizeof g);
memset(match, -1, sizeof match);
while (k--) {
int t, a, b;
cin >> t >> a >> b;
if (!a || !b) continue;
g[a][b] = true; // 只需要记录从第一个集合到第二个集合的边即可
}
int res = 0;
for (int i = 1; i < n; i++) {
memset(st, 0, sizeof st);
if (find(i)) res++;
}
cout << res << endl;
}
return 0;
}
AcWing 378. 骑士放置
问题描述
-
问题链接:AcWing 378. 骑士放置
分析
- 将格子看成图中的点,如果两个马是可以相互攻击到的,则在这两个格子之间连一条边。则剩下的问题就变成了:我们可以最多可以选择多少点,使得这些点之间没有边。即最大独立集问题。
- 下面验证建立的图是二分图。如下图,可以看到马向八个方向跳的格子都是和其所在的格子沿着是不同的(这是因为偏移量一定是一个计数,一个偶数):
- 根据分析,因此最终的结果为 n × m − T − n \times m - T - n×m−T−最大匹配数量。
代码
- C++
#include <iostream>
#include <cstring>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int n, m, k;
bool g[N][N]; // 代表是否有障碍物
PII match[N][N];
bool st[N][N];
int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};
bool find(int x, int y) {
for (int i = 0; i < 8; i++) {
int a = x + dx[i], b = y + dy[i];
if (a < 1 || a > n || b < 1 || b > m) continue;
if (g[a][b] || st[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;
}
int main() {
cin >> n >> m >> k;
for (int i = 0; i < k; i++) {
int x, y;
cin >> x >> y;
g[x][y] = true;
}
int res = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
if (g[i][j] || (i + j) % 2) continue;
memset(st, 0, sizeof st);
if (find(i, j)) res++;
}
cout << n * m - k - res << endl;
return 0;
}
AcWing 379. 捉迷藏
问题描述
-
问题链接:AcWing 379. 捉迷藏
分析
- 分析题目可知,只要两个点存在一条路径,两者就是可以相互看到的(相当于人的视线可以拐弯的)。
- 该题相当于问:给定我们一个有向无环图,让我们选择尽可能多的点,使得这些点任意两点之间都不能相互到达。
- 首先说答案:答案 = 最小路径重复点覆盖的数量cnt。下面给出证明:
(1)首先一定有 K ≤ c n t K \le cnt K≤cnt,否则一条路径上会选出两个点,会相互看见。接着证明K可以取到cnt。
(2)我们找出这cnt条路径的终点,这些终点肯定两两都不相同,否则的话某条路径上可以删除重复的终点,仍然符合要求。这些记为集合V,然后我们将这些终点所有能反向到达的点的集合记为 n e x t ( V ) next(V) next(V)。
(2.1)如果有 V ⋂ n e x t ( V ) = ∅ V \bigcap next(V) = \emptyset V⋂next(V)=∅,意味着我们从E出发是不可能到达V内部的点的,此时选择E中的cnt个点是符合要求的。
(2.2)如果有 V ⋂ n e x t ( V ) ≠ ∅ V \bigcap next(V) \neq \emptyset V⋂next(V)=∅,则我们可以从V中选择出某一个终点 v i v_i vi,我们让 v i v_i vi沿着边反向向前走(向前走到的点仍然记为 v i v_i vi),直到走到满足条件 v i ∉ n e x t ( V − { v i } ) v_i \not \in next(V -\{v_i\}) vi∈next(V−{vi}),一定是可以找到这样点 v i v_i vi的,否则这条路径就是多余的。如果仍然不为空,继续选择另一个终点进行这样的操作,直到交集为空集为止。
- 根据上面的讲解可知:原图的最小路径重复点覆盖t == 原图总点数n - 原图对应闭包图构造的二分图的最大匹配数m。
代码
- C++
#include <iostream>
#include <cstring>
using namespace std;
const int N = 210, M = 30010;
int n, m;
bool d[N][N]; // 邻接矩阵
int match[N];
bool st[N];
bool find(int x) {
for (int i = 1; i <= n; i++)
if (d[x][i] && !st[i]) {
st[i] = true;
if (match[i] == 0 || find(match[i])) {
match[i] = x;
return true;
}
}
return false;
}
int main() {
scanf("%d%d", &n, &m);
while (m--) {
int a, b;
scanf("%d%d", &a, &b);
d[a][b] = true;
}
// 传递闭包
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
d[i][j] |= d[i][k] & d[k][j];
int res = 0;
for (int i = 1; i <= n; i++) {
memset(st, 0, sizeof st);
if (find(i)) res++;
}
printf("%d\n", n - res);
return 0;
}
3. 力扣上二分图题目
Leetcode 0785 判断二分图
问题描述
-
问题链接:Leetcode 0785 判断二分图
分析
- 染色法求二分图,模板题。
代码
- C++
/**
* 执行用时:32 ms, 在所有 C++ 提交中击败了40.35%的用户
* 内存消耗:14.2 MB, 在所有 C++ 提交中击败了5.03%的用户
*/
class Solution {
public:
vector<int> color;
vector<vector<int>> g;
bool isBipartite(vector<vector<int>> &graph) {
g = graph;
int n = g.size();
color.resize(n, 0);
for (int i = 0; i < n; i++)
if (!color[i])
if (!dfs(i, 1))
return false;
return true;
}
bool dfs(int u, int c) {
color[u] = c;
for (auto w : g[u]) {
if (!color[w]) {
if (!dfs(w, 3 - c)) return false;
} else if (color[w] == c) return false;
}
return true;
}
};
- Java
/**
* Date: 2021/4/13 8:59
* 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
* 内存消耗:38.9 MB, 在所有 Java 提交中击败了65.81%的用户
*/
public class Solution {
int[] color; // 0代表当前节点还未染色, 1、2分别表示两种颜色
int[][] g;
public boolean isBipartite(int[][] graph) {
g = graph;
int n = g.length; // 图中顶点数
color = new int[n];
for (int i = 0; i < n; i++)
if (color[i] == 0)
if (!dfs(i, 1))
return false;
return true;
}
private boolean dfs(int u, int c) {
color[u] = c;
for (int w : g[u]) {
if (color[w] == 0) {
if (!dfs(w, 3 - c)) return false;
} else if (color[w] == c) return false;
}
return true;
}
}
Leetcode LCP 04 覆盖
问题描述
-
问题链接:Leetcode LCP 04 覆盖
分析
- 和AcWing 372. 棋盘覆盖一样,可以参考该题的分析。
代码
- C++
/**
* 执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
* 内存消耗:8.2 MB, 在所有 C++ 提交中击败了80.81%的用户
*/
class Solution {
public:
typedef pair<int, int> PII;
int n, m;
vector<vector<int>> g;
PII match[10][10];
bool st[10][10];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
bool find(int x, int y) {
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (st[a][b] || g[a][b]) continue;
st[a][b] = true;
PII t = match[a][b];
if (t.first == -1 || find(t.first, t.second)) {
match[a][b] = {x, y};
return true;
}
}
return false;
}
int domino(int _n, int _m, vector<vector<int>> &broken) {
n = _n, m = _m;
g = vector<vector<int>>(n, vector<int>(m, 0));
for (int i = 0; i < broken.size(); i++) {
int a = broken[i][0], b = broken[i][1];
g[a][b] = 1;
}
memset(match, -1, sizeof match);
int res = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
if ((i + j) % 2 && !g[i][j]) {
memset(st, 0, sizeof st);
if (find(i, j)) res++;
}
return res;
}
};