【算法学习笔记】二分图

二分图

图论的证明是真的好复杂。。。我也就是听一听,大致理解一下,苯人真的不会证qaq
二分图的板子还是很简单的,难就难在把问题抽象成二分图中对应的问题(图论之难大多在此)
所以多做题吧...

染色法判二分图

看是否存在奇环

板子:

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] == -1) {
            if (!dfs (j, !c))   return false;
        }
        else if (color[j] == c) return false;
    }
    return true;
}

bool check () {
    memset (color, -1, sizeof color);
    for (int i = 1; i <= n; i++) {
        if (color[i] == -1) {
            if (!dfs (i, 0))    return false;
        }
    }
    return true;
}

典中典题目:P1525 [NOIP2010 提高组] 关押罪犯

#include <bits/stdc++.h>

using namespace std;
const int N = 20005, M = 200005;
int h[N], e[M], ne[M], w[M], idx;
int color[N], n, m;

void add (int a, int b, int c) {
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

bool dfs (int u, int c, int maxn) {
	color[u] = c;
	for (int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if (w[i] <= maxn)	continue; //w[i]
		if (color[j] == -1) {
			if (!dfs (j, !c, maxn))	return false;
		}
		else if (color[j] == c)	return false;
	}
	return true;
}

bool check (int x) {
	memset (color, -1, sizeof color);
	for (int i = 1; i <= n; i++) {
		if (color[i] == -1) {
			if (!dfs (i, 0, x))	return false;
		}
	}
	return true;
}

int main () {
	memset (h, -1, sizeof h);
	scanf ("%d%d", &n, &m);
	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);
}

//二分边长

二分图的最大匹配

增广路径:从左匹配点出发,先沿非匹配边走,再沿匹配边走...依次交替,最后落在右非匹配点的一条路。
最大匹配 \(\leftrightarrow\) 不存在增广路径

匈牙利算法

板子:

bool find (int u) {
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (vis[j])     continue;
        vis[j] = true;
        if (!match[j] || find (match[j])) {
            match[j] = u;
            return true; //匹配上一对
        }
    }
    return false;
}

例题:棋盘覆盖

抽象成二分图:
把每个格子视为点,向能放骨牌的点连一条边,所求转化为所选边无公共点的情况下,最多能选多少边(转化为匹配问题)
隔一个染色又可以转化为二分图(相邻格子颜色不同)

#include <bits/stdc++.h>

using namespace std;
typedef pair<int, int> pii;
const int N = 105;
int n, k;
bool vis[N][N], stk[N][N];
pii match[N][N];
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

bool Range (int x, int y) {
    if (x < 1 || x > n || y < 1 || y > n)   return false;
    return true;
}

bool find (pii p) {
    int x = p.first, y = p.second;
    for (int i = 0; i < 4; i++) {
        int xx = x + dx[i], yy = y + dy[i];
        if (!Range (xx, yy))    continue;
        if (vis[xx][yy] || stk[xx][yy])     continue;
        //if ((xx + yy) & 1)  continue;
        vis[xx][yy] = true;
        if (!match[xx][yy].first || find (match[xx][yy])) {
            match[xx][yy] = p;
            return true;
        }
    }
    return false;
}

int main () {
    cin >> n >> k;
    while (k --) {
        int x, y;
        cin >> x >> y;
        stk[x][y] = true;
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (stk[i][j])  continue;
            memset (vis, false, sizeof vis);
            if (((i + j) & 1) && !vis[i][j]) {
                if (find ({i, j}))    ans ++;
            }
        }
    }
    cout << ans;
}

二分图的最小点覆盖

最小点覆盖:从图中选出最少的点,使得每一条边的两个端点至少有一个点被选出来
二分图中,最小点覆盖=最大匹配数

例题:机器任务
直接利用 二分图中,最小点覆盖=最大匹配数 这个结论,套板子即可

注意多组数据之间的清空问题

#include <bits/stdc++.h>

using namespace std;
const int N = 105;
int n, m, k, x;
int match[N];
bool vis[N], g[N][N];

bool find (int u) {
    for (int i = 0; i < m; i++) {
        if (vis[i] || !g[u][i]) continue;
        vis[i] = true;
        if (!match[i] || find (match[i])) {
            match[i] = u;
            return true;
        }
    }
    return false;
}

int main ()  {
    while (cin >> n, n) {
        cin >> m >> k;
        memset (g, false, sizeof g);
        memset (match, 0, sizeof match);
        while (k --) {
            int a, b;
            cin >> x >> a >> b;
            if (a == 0 || b == 0)   continue;
            g[a][b] = true;
        }
        int ans = 0;
        for (int i = 0; i < n; i++) {
            memset (vis, false, sizeof vis);
            if (find (i))   ans ++;
        }
        cout << ans << endl;
    }
}

最大独立集

选出最多的点,使得任意两点之间没有边
二分图中,求最大独立集等价于 \(\leftrightarrow\) 去掉最少的点,使得所有边被破坏 \(\leftrightarrow\) 找最小点覆盖
即 最大独立集 = \(n-m\)\(n\) 是总点数,\(m\) 是最小点覆盖的最小点数

例题:骑士放置
把能攻击到的两点连一条边,就转化为求最大独立集问题了,然后证明他是二分图的过程与棋盘覆盖那道题类似。
即 所求为 \(n-k-m\)

#include <bits/stdc++.h>

using namespace std;
typedef pair<int, int> pii;
const int N = 105;
bool vis[N][N], stk[N][N];
int n, m, k;
int dx[] = {1, 2, -1, 2, -2, 1, -1, -2};
int dy[] = {2, 1, 2, -1, 1, -2, -2, -1};
pii match[N][N];

bool Range (int x, int y) {
    if (x < 1 || x > n || y < 1 || y > m)   return false;
    return true;
}

bool find (pii p) {
    int x = p.first, y = p.second;
    for (int i = 0; i < 8; i++) {
        int xx = x + dx[i], yy = y + dy[i];
        if (!Range (xx, yy))    continue;
        if (vis[xx][yy] || stk[xx][yy]) continue;
        vis[xx][yy] = true;
        if (!match[xx][yy].first || find (match[xx][yy])) {
            match[xx][yy] = p;
            return true;
        }
    }
    return false;
}

int main () {
    cin >> n >> m >> k;
    for (int i = 0; i < k; i++) {
        int x, y;
        cin >> x >> y;
        stk[x][y] = true;
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if ((i + j) & 1)    continue;
            if (stk[i][j])  continue;
            memset (vis, false, sizeof vis);
            if (find ({i, j}))   ans ++;
        }
    }
    cout << n * m - ans - k << endl;
}

最小路径点覆盖

最小路径覆盖问题:DAG上,用最少的互不相交的路径将所有点覆盖。
数值上等于最大独立集

拆点,由于是DAG,所以每个点最多覆盖一个边。
原图终点(没有出边)对应新图的左部未匹配点。
求原图中互不相交路径数\(\leftrightarrow\)求路径终点数最少\(\leftrightarrow\)求左部非匹配点最少\(\leftrightarrow\)求最大匹配

拓展:最小路径重复点覆盖

  1. 求传递闭包,得新图G'
  2. 在G'上求最小路径覆盖

例题:捉迷藏
具体的证明我还是自己写不出来(想不到),所以可以看看题姐:

#include <bits/stdc++.h>

using namespace std;
const int N = 205, M = 30005;
int n, m;
int match[N];
bool vis[N], g[N][N];

bool find (int u) {
    for (int i = 1; i <= n; i++) {
        if (vis[i] || !g[u][i]) continue;
        vis[i] = true;
        if (!match[i] || find (match[i])) {
            match[i] = u;
            return true;
        }
    }
    return false;
}

int main () {
    cin >> n >> m;
    while (m --) {
        int a, b;
        cin >> a >> b;
        g[a][b] = true;
    }

    for (int k = 1; k <= n; k++) { //floyd求传递闭包
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                g[i][j] |= g[i][k] & g[k][j];
            }
        }
    }

    int ans = 0;
    for (int i = 1; i <= n; i++) {
        memset (vis, false, sizeof vis);
        if (find (i))   ans ++;
    }
    cout << n - ans;
}

补题

说起来倒回来学二分图是为了补这题:19ICPC南京-J

Reference

AcWing算法提高课

OI-Wiki:最小点覆盖

洛谷二分图板刷:https://www.luogu.com.cn/problem/list?keyword=&page=1&tag=187&orderBy=difficulty&order=asc

posted @ 2022-11-15 21:12  Sakana~  阅读(43)  评论(0编辑  收藏  举报