二分图匹配(匈牙利算法,hungray)

二分图

二分图难点在于如何建图

二分图最大匹配 : K   (匈牙利算法)
二分图最小顶点覆盖 : 等价于K   (最少的顶点覆盖所有边)
二分图的最大独立集 : 等价于顶点数N - K  (去除尽可能少的点,使所有边消失,所剩下最多的独立点)
DAG图最小路径覆盖 : 等价于顶点数N - K  (有向无环图用最少的路径覆盖所有点,每条路径所走的点各不相同)

1、过山车

HDU2063 过山车

二分图最大匹配(匈牙利算法)

6 3 3
1 1
1 2
1 3
2 1
2 3
3 1
0

6条边(关系) 左图3个点 右图3个点
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

#define N 500
#define M 1000

int vis[N + 5], flag[N + 5], head[M + 5], edge[M + 5], nxt[M + 5], tot;

#define MS(a, b) memset(a, b, sizeof(a))
void Init() {
    MS(vis, -1);
    MS(head, 0);
    MS(edge, 0);
    MS(nxt, 0);
    tot = 0;
}
#undef MS

void add(int k) {
    int s, e;
    for (int i = 0; i < k; ++i) {
        scanf("%d%d", &s, &e);
        ++tot;
        edge[tot] = e;
        nxt[tot] = head[s];
        head[s] = tot;
    }
    return ;
}

bool Hungary(int s) {
    for (int i = head[s]; i; i = nxt[i]) {
        if (flag[edge[i]])  continue;
        flag[edge[i]] = 1;
        if (vis[edge[i]] == -1 || Hungary(vis[edge[i]])) {
            vis[edge[i]] = s;
            return true;
        }
    }
    return false;
}

int main() {
    int k, n, m;
    while (scanf("%d", &k) && k) {
        Init();
        scanf("%d%d", &n, &m);
        add(k);
        int ans = 0;
        for (int i = 1; i <= n; ++i) {
            memset(flag, 0, sizeof(flag));
            if (Hungary(i))  ++ans;
        }
        printf("%d\n", ans);
    }
    return 0;
}

2、Machine Schedule

HDU1150 Machine Schedule

二分图最小顶点覆盖
注意:两台机器的初始状态都为0,需预处理0状态
题目意思:
有A,B两种机器,A机器上有n种模具,B机器上有m种模具,有k个任务,每个任务可以在既可以在A机器上的x模具上生产,也可以在B机器上的y模具上生产。A,B机器都可以切换模具,开始的A,B都为0模具,问最少切换多少次模具。可以完成所有的任务。

题目思路:
如果我们把A机器的n个模具作为二分图的左部,B机器的m个模具作为二分图的右部,然后把每个任务当成一条连接左部x和右部y的边,问题就转化为选择最少的点,覆盖所有的边,边覆盖的定义为该边至少有一个端点在所选点集中。(用最少的模具完成所有任务)。
有这么一个结论:二分图最大匹配 = 二分图最小点覆盖

5 5 10
0 1 1
1 1 2
2 1 3
3 1 4
4 2 1
5 2 2
6 2 3
7 2 4
8 3 3
9 4 3
0

3
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

#define N 100
#define M 1000

int vis[N + 5], flag[N + 5], head[M + 5], edge[M + 5], nxt[M + 5], tot;

#define MS(a, b) memset(a, b, sizeof(a))
void Init() {
    MS(vis, -1);
    MS(flag, 0);
    MS(head, 0);
    MS(edge, 0);
    MS(nxt, 0);
    tot = 0;
    return ;
}

void add(int k) {
    int id, s, e;
    for (int i = 1; i <= k; ++i) {
        scanf("%d%d%d", &id, &s, &e);
        if (s == 0 || e == 0)  continue;
        ++tot;
        edge[tot] = e;
        nxt[tot] = head[s];
        head[s] = tot;
    }
    return ;
}

bool hungary(int s) {
    for (int i = head[s]; i; i = nxt[i]) {
        if (flag[edge[i]])  continue;
        flag[edge[i]] = 1;
        if (vis[edge[i]] == -1 || hungary(vis[edge[i]])) {
            vis[edge[i]] = s;
            return true;
        }
    }
    return false;
}

int main() {
    int n, m, k;
    while (scanf("%d", &n) && n) {
        Init();
        scanf("%d%d", &m, &k);
        add(k);
        int ans = 0;
        for (int i = 1; i <= n; ++i) {
            MS(flag, 0);
            if (hungary(i))  ++ans;
        }
        printf("%d\n", ans);
    }
    return 0;
}

3、Girls and Boys

HDU1068 Girls and Boys

二分图最大独立集 = 节点数 - 最小节点覆盖
注意此题数据在一个集合中

7
0: (3) 4 5 6
1: (2) 4 6
2: (0)
3: (0)
4: (2) 0 1
5: (1) 0
6: (2) 0 1
3
0: (2) 1 2
1: (1) 0
2: (1) 0

5
2
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

#define N 100000

int vis[N + 5], flag[N + 5], head[N + 5], edge[N + 5], nxt[N + 5], tot;

#define MS(a, b) memset(a, b, sizeof(a))
void Init() {
    MS(vis, -1);
    MS(flag, 0);
    MS(head, 0);
    MS(edge, 0);
    MS(nxt, 0);
    tot = 0;
    return ;
}

void add(int t) {
    int s, n, e;
    for (int i = 0; i < t; ++i) {
        scanf("%d: (%d)", &s, &n);
        while (n--) {
            scanf("%d", &e);
            ++tot;
            edge[tot] = e;
            nxt[tot] = head[s];
            head[s] = tot;
        }
    }
    return ;
}

bool hungary(int s) {
    for (int i = head[s]; i; i = nxt[i]) {
        if (flag[edge[i]])  continue;
        flag[edge[i]] = 1;
        if (vis[edge[i]] == -1 || hungary(vis[edge[i]])) {
            vis[edge[i]] = s;
            return true;
        }
    }
    return false;
}

int main() {
    int t;
    while (~scanf("%d", &t)) {
        Init();
        add(t);
        int Max = 0;
        for (int i = 0; i < t; ++i) {
            MS(flag, 0);
            if (hungary(i))  ++Max;
        }
        printf("%d\n", t - Max / 2);
    }
    return 0;
}

4、Air Raid

HDU1151 Air Raid

DAG图最小路径覆盖

什么是DAG图的最小路径覆盖
用尽量少的不相交简单路径覆盖有向无环图(DAG)的所有顶点,这就是——DAG图的最小路径覆盖问题。
一句话记忆:
最小路径覆盖就是用最少的路覆盖所有的点。


有一个城镇,它的所有街道都是单行的,并且每条街道都是和两个路口相连。同时已知街道不会形成回路。
你的任务是编写程序求最小数量的伞兵,这些伞兵可以访问(visit)所有的路口。对于伞兵的起始降落点不做限制。

2
4
3
3 4
1 3
2 3
3
3
1 3
1 2
2 3

2
1

在这里插入图片描述

在这里插入图片描述

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

#define N 120

int vis[N + 5], flag[N + 5], head[N + 5], edge[N + 5], nxt[N + 5], tot;

#define MS(a, b) memset(a, b, sizeof(a))
void Init() {
    MS(vis, -1);
    MS(flag, 0);
    MS(head, 0);
    MS(edge, 0);
    MS(nxt, 0);
    tot = 0;
    return ;
}

void add(int m) {
    int s, e;
    for (int i = 0; i < m; ++i) {
        scanf("%d%d", &s, &e);
        ++tot;
        edge[tot] = e;
        nxt[tot] = head[s];
        head[s] = tot;
    }
    return ;
}

bool hungary(int s) {
    for (int i = head[s]; i; i = nxt[i]) {
        if (flag[edge[i]])  continue;
        flag[edge[i]] = 1;
        if (vis[edge[i]] == -1 || hungary(vis[edge[i]])) {
            vis[edge[i]] = s;
            return true;
        }
    }
    return false;
}

int main() {
    int t, n, m;
    scanf("%d", &t);
    while (t--) {
        Init();
        scanf("%d%d", &n, &m);
        add(m);
        int Max = 0;
        for (int i = 1; i <= n; ++i) {
            MS(flag, 0);
            if (hungary(i))  ++Max;
        }
        printf("%d\n", n - Max);
    }
    return 0;
}

5、棋盘游戏

HDU1281 棋盘游戏

二分图最大匹配
用行号和列号创建二分图,每一行只能匹配一列,同理每列只能匹配一行
重要点:去除该点后,最大匹配数会变小
如何找到重要点:求得最大匹配数之后,暴力搜索每一个点,去除该点看是否会改变最大匹配数
注意:题目图中的点,在二分图中用边来反映。重复使用匈牙利算法时,注意清空对象数组

在这里插入图片描述

在这里插入图片描述

3 3 4
1 2
1 3
2 1
2 2
3 3 4
1 2
1 3
2 1
3 2

Board 1 have 0 important blanks for 2 chessmen.
Board 2 have 3 important blanks for 3 chessmen.
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

#define N 100
#define M 10000

int head[N + 5], edge[M + 5], nxt[M + 5], flag[N + 5], pre[N + 5], tot;

#define MS(a, b) memset(a, b, sizeof(a))

void init() {
    MS(head, 0);
    MS(edge, 0);
    MS(nxt, 0);
    MS(pre, -1);
    tot = 0;
    return ;
}

void add(int n) {
    init();
    int s, e;
    for (int i = 1; i <= n; ++i) {
        scanf("%d%d", &s, &e);
        ++tot;
        edge[tot] = e;
        nxt[tot] = head[s];
        head[s] = tot;
    }
    return ;
}

bool __hungary(int s, int x, int y) {
    for (int i = head[s]; i; i = nxt[i]) {
        if (flag[edge[i]] || (s == x && edge[i] == y))  continue;
        flag[edge[i]] = 1;
        if (pre[edge[i]] == -1 || __hungary(pre[edge[i]], x, y)) {
            pre[edge[i]] = s;
            return true;
        }
    }
    return false;
}

bool hungary(int s, int x, int y) {
    MS(flag, 0);
    return __hungary(s, x, y);
}

int main() {
    int n, m, k, t = 0;
    while (~scanf("%d%d%d", &n, &m, &k)) {
        ++t;
        add(k);
        int ans = 0, cnt = 0;
        for (int i = 1; i <= n; ++i) {
            if (hungary(i, 0, 0))  ++ans;
        }

        for (int i = 1; i <= n; ++i) {
            for (int j = head[i]; j; j = nxt[j]) {
                MS(pre, -1);
                int step = 0;
                for (int z = 1; z <= n; ++z) {
                    if (hungary(z, i, edge[j]))  ++step;
                }
                if (step != ans)  ++cnt;
            }
        }
        printf("Board %d have %d important blanks for %d chessmen.\n", t, cnt, ans);
    }
    return 0;
}

6、Fire Net

HDU1045 Fire Net

在这里插入图片描述

题目大意:
有一个Map[N][N],空白方格上放置墙壁(黑方格),炮台(黑色圆),要求在已知墙壁的情况下放置最多的炮台(最大匹配),限制条件:同一行同一列只能放置一个炮台(墙壁可以起到隔断作用)。
如何建图:横竖分区。先看每一列,同一列相连的空地同时看成一个点,显然这样的区域不能够同时放两个点。这些点作为二分图的X部。同理在对所有的行用相同的方法缩点,作为Y部。连边的条件是两个区域有相交部分。最后求最大匹配就是答案。

原图
.X..   
....		
XX..
....
按行编号:
1022
3333
0044
5555
按列编号:
1056
1356
0056
2456

可按行图和列图的对应位置点进行建图
#include <iostream>
#include <cstdio>
#include <cstring>
#include <unordered_map>

using namespace std;

#define N 30

char nums[N][N];
int line[N][N], col[N][N], pre[N], flag[N], head[N], edge[N], nxt[N], tot;

#define MS(a, b) memset(a, b, sizeof(a))
void init() {
    MS(nums, 0);
    MS(line, 0);
    MS(col, 0);
    MS(head, 0);
    MS(edge, 0);
    MS(nxt, 0);
    MS(pre, -1);
    tot = 0;
    return ;
}

void add(int n) {
    init();
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            cin >> nums[i][j];
        }
    }
    int cnt_line = 0, cnt_col = 0;
    for (int i = 1; i <= n; ++i) {
        ++cnt_line;
        ++cnt_col;
        for (int j = 1; j <= n; ++j) {
            if (nums[i][j] == 'X')  ++cnt_line;
            else  line[i][j] = cnt_line;
            if (nums[j][i] == 'X')  ++cnt_col;
            else  col[j][i] = cnt_col;
        }
    }
    unordered_map<int, unordered_map<int, bool> > mp;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (nums[i][j] == 'X')  continue;
            int s = line[i][j], e = col[i][j];
            if (mp[s].count(e))  continue;
            mp[s][e] = true;
            ++tot;
            edge[tot] = e;
            nxt[tot] = head[s];
            head[s] = tot;
        }
    }
    return ;
}

bool __hungary(int s) {
    for (int i = head[s]; i; i = nxt[i]) {
        if (flag[edge[i]])  continue;
        flag[edge[i]] = 1;
        if (pre[edge[i]] == -1 || __hungary(pre[edge[i]])) {
            pre[edge[i]] = s;
            return true;
        }
    }
    return false;
}

bool hungary(int s) {
    MS(flag, 0);
    return __hungary(s);
}

int main() {
    int n;
    while (scanf("%d", &n) && n) {
        add(n);
        int ans = 0;
        for (int i = 1; i <= N; ++i) {
            if (hungary(i))  ++ans;
        }
        printf("%d\n", ans);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值