c

casperwu

V1

2022/07/20阅读:8主题:橙心

二分图

二分图与匈牙利算法

1. 定义

设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边所关联的两个顶点i和j分别属于这两个不同的顶点集,则称图G为一个二分图。

2. 二分图的判定

给定一个图,可以使用染色法来判断这个图是否是一个二分图。

// 使用dfs方法染色
#include <iostream>
#include <cstring>

using namespace std;

const int N = 100005, M = 200005;

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++;
}

// 返回true表示是二分图, false 表示不是二分图
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]) {
            // 给另一端染成 2 号颜色(3 - 1 = 2)
            if(!dfs(j, 3 - c)) return false;
        } else if(color[j] == c) return false;  // 一条边的两端颜色相同, 表示染色失败, 不是二分图
    }
    return true;
}

int main() {
    int n, m;
    
    memset(h, -1sizeof(h));
    
    cin >> n >> m;
    int a, b;
    while(m--) {
        cin >> 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)) { // 给i点染成1号颜色, 递归染色, 染色失败, 直接跳出循环
                flag = false;
                break;
            }
        }
    }
    
    if(flag) puts("Yes");
    else puts("No");
    
    return 0;
}

3. 二分图的用途

可用于处理最大匹配的问题。常用的算法是:匈牙利算法。

3.1 题目

骑士放置
骑士放置

3.2 题意理解

如果两个格子之间能相互攻击,则在这两个格子之间建立一条边; 原题就等价于 选最多的点使得两两之间没有边。

能使用匈牙利算法来解决的前提条件是:这个图是一个二分图。 证明此题中的骑士摆放是一个二分图:

  1. 给棋盘染色, 横纵坐标和为偶数的染成黑色, 横纵坐标和奇数染成白色. 此时可以发现当棋子位于黑色格子时,可以跳的格子都是白色的.因此所有的边都在两个集合之间。

  2. 假设当前格子的坐标为(x, y),则当跳了一步之后,nx + ny 的奇偶性与 x + y 的奇偶性是相反的。因为在跳的过程中坐标值的变换是一个奇数, 因此一个数加减一个奇数,它的奇偶性一定会发生变换。因此是一个二分图。

3.3 示例代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef pair<intint> PII;
const int N = 110;

// g[][] 记录某个点能否可以放棋子
bool g[N][N];
bool st[N][N];
PII match[N][N];  // 记录某个点的匹配点

int n, m, k;
int dx[] = {-2-11221-1-2};
int dy[] = {1221-1-2-2-1};

bool find(int x, int y) {
    for(int i = 0; i < 8; i++) {
        int nx = x + dx[i], ny = y + dy[i];
        
        if(nx < 1 || nx > n || ny < 1 || ny > m) continue;
        if(g[nx][ny]) continue;
        if(st[nx][ny]) continue;
        
        st[nx][ny] = true;
        PII t = match[nx][ny];
        
        if(t.first == 0 || find(t.first, t.second)) {
            match[nx][ny] = {x, y};
            return true;
        }
    }
    
    return false;
}

int main() {
    cin >> n >> m >> k;
    
    for(int i = 0; i < k; i++) {
        int a, b; cin >> a >> b;
        g[a][b] = true;
    }
    
    int ans = 0;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            if((i + j) % 2 && !g[i][j]) {
                memset(st, 0sizeof(st));
                if(find(i, j)) ans++;
            }
        }
    }
    
    cout << n * m - k - ans;
    
    return 0;
}

分类:

后端

标签:

后端

作者介绍

c
casperwu
V1