二分图最小点覆盖构造方案+König定理证明
前言
博主很笨 ,如有纰漏,欢迎在评论区指出讨论。
二分图的最大匹配使用 \(Dinic\) 算法进行实现,时间复杂度为 \(O(n\sqrt{e})\),其中, \(n\)为二分图中左部点的数量, \(e\) 为二分图中的边数。若是匈牙利算法,时间复杂度为 \(O(nm)\) , \(m\) 为二分图中右部点的数量,不建议使用。
König定理
定理内容:二分图最小点覆盖的点的数量等于二分图最大匹配的边的数量。
构造方法 \(+\) 简单证明:
首先求出二分图中的最大匹配,建议使用 \(Dinic\) 。
从每一个非匹配点出发,沿着非匹配边正向进行遍历,沿着匹配边反向进行遍历到的点进行标记。选取左部点中没有被标记过的点,右部点中被标记过的点,则这些点可以形成该二分图的最小点覆盖。
遍历代码实现如下:
void dfs(int now) {
vis[now] = true;
int SIZ = v[now].size();
for(int i = 0; i < SIZ; i++) {
int next = v[now][i].to;
if(vis[next] || !v[now][i].val)//正向边的容量为0说明是匹配边,反向边的容量为0说明是非匹配边
continue;
dfs(next);
}
}
那么就有以下性质:
- 若该点为左边的非匹配点,则这个点必被访问,因为这个点是整个 \(dfs\) 的起点
- 若该点为右边的非匹配点,则这个点必不会被访问,若是由左边的非匹配点才到达了这个点,那么可以将这条边变为匹配边,则匹配数 \(+1\) ,与最大匹配相冲突。若是左边的匹配点才到达了这个点,那么这个点的路径为左边非匹配点 → 右边匹配点 → 左边非匹配点 → 右边匹配点 → …… → 左边匹配点 → 右边非匹配点 ,很明显,上述路径为增广路,与最大匹配相冲突。所以,右边的非匹配点必不会被访问。
- 对于一组匹配点,要么两个都被标记,要么都不被标记。因为左部的匹配点是由右部的匹配点来遍历到的,出现必然成双成对。
有了上述的三条性质,可以发现:按照选取左部点中没有被标记过的点,右部点中被标记过的点的规则,选出来的点的点数必然为最大匹配的边数。左部的非匹配点必然被访问,则必不会被选,右部的非匹配点必不会被访问,则必不会被选。而第三条性质决定了,对于一组匹配点,会选择有且仅有一个点。故而选出的点的点数等于最大匹配的边数。
其次需要解决一个问题:保证这些点覆盖了所有的边。具体可以分为四类:
- 左部为非匹配点,右部为非匹配点。性质二已经讨论过,不可能出现这种情况,出现就不满足最大匹配的前提。
- 左部为匹配点,右部为非匹配点。同理性质二,路径类似,会出现增广路,那么这个左部的匹配点一定没有被访问过,必然被选。
- 左部为匹配点,右部为匹配点。一对匹配点中必选一个。
- 左部为非匹配点,右部为匹配点。这条边为非匹配边,而起点就是从左部的非匹配点点开始,那么右部的这个点必然被访问过,必然被选。
最后在确保这是最小的方案:一条边都只选了一个点,不存在浪费。
如上,证毕。
题目来源:COCI 2019/2020 Contest #6 T4. Skandi
题目大意
给定一个 \(n\times m\) 的矩阵,其中的白色点为 \(0\) , 黑色点为 \(1\) 。黑色点可以往下一直扩展到底部,把白色点变成蓝色点,直到遇到黑色点为止。同理,也可向右扩展。问整个矩阵经过最小多少次扩展才能扩展为整个矩阵到不存在白色,并打印出每次扩展是从哪个点开始的,并打印出扩展方向。题目满足第一行第一列一定为黑色点。
思路
一道建模题。
一个白色点变为蓝色点只有两种方法,从它上方或左方的黑色点扩展而来,且只需要一个点扩展即可。可以考虑到最小点覆盖问题。
由于对于一个黑色点来说,它可以往右或往下扩展。那么它就有两个身份,也就是说一个点拥有两个编号。一个编号为把整个矩阵拉成一条链的顺序,另一个编号为前一个编号 \(+n\times m\) ,这样不会发生冲突。获得编号的函数:
int GetHash(int i, int j) {
return (i - 1) * m + j;
}
那么不难发现一个白色点,与其相关的是一个编号 \(\leqslant n\times m\) 的点,和一个编号 \(>n\times m\) 的点。把这两个点连接起来,就是一张二分图。
问题就转换为找这张图的最小点覆盖问题。使用 \(Dinic\) ,在根据上述 \(König\) 定理构造即可。
边数为白点的个数,左部点为黑点的个数,则时间复杂度为 \(O(nm\sqrt{nm})\) ,即 \(O(n^{\frac{3}{2}}m^{\frac{3}{2}})\) ,本题的 \(n\) , \(m\) 均小于 \(500\) ,大概能够在 \(1s\) 内求出答案。
C++代码
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
#define INF 0x3f3f3f3f
const int MAXN = 1e6 + 5;
const int MAXM = 5e2 + 5;
struct Node {
int to, val, rev;//依次为:下一个点,边的容量,相反的边的编号
Node() {}
Node(int T, int V, int R) {
to = T;
val = V;
rev = R;
}
};
vector<Node> v[MAXN];//用vector存图的癖好...
int dn[MAXN], rt[MAXN];//预处理白色点可以右那两个点扩展而来
queue<int> q;
int de[MAXN], be[MAXN];
int twin[MAXN];
bool vis[MAXN];
int n, m, s, t;
int arr[MAXM][MAXM];
bool bfs() {//将残量网络分层
bool flag = 0;
memset(de, 0, sizeof(de));
while(!q.empty())
q.pop();
q.push(s);
de[s] = 1; be[s] = 0;
while(!q.empty()) {
int now = q.front();
q.pop();
int SIZ = v[now].size();
for(int i = 0; i < SIZ; i++) {
int next = v[now][i].to;
if(v[now][i].val && !de[next]) {
q.push(next);
be[next] = 0;
de[next] = de[now] + 1;
if(next == t)
flag = 1;
}
}
}
return flag;
}
int dfs(int now, int flow) {//沿着增广路增广
if(now == t || !flow)
return flow;
int i, surp = flow;
int SIZ = v[now].size();
for(i = be[now]; i < SIZ && surp; i++) {
be[now] = i;
int next = v[now][i].to;
if(v[now][i].val && de[next] == de[now] + 1) {
int maxnow = dfs(next, min(surp, v[now][i].val));
if(!maxnow)
de[next] = 0;
v[now][i].val -= maxnow;
v[next][v[now][i].rev].val += maxnow;
surp -= maxnow;
}
}
return flow - surp;
}
int Dinic() {//网络最大流,亦可用于二分图匹配
int res = 0;
int flow = 0;
while(bfs())
while(flow = dfs(s, INF))
res += flow;
return res;
}
int GetHash(int i, int j) {//获取点的编号
return (i - 1) * m + j;
}
void Down(int now, int i, int j) {//黑点向下扩展,每个白点最多遍历到一次
if(i != now)
dn[GetHash(now, j)] = GetHash(i, j);
if(arr[now + 1][j] == 2)
Down(now + 1, i, j);
}
void Right(int now, int i, int j) { //黑点向右扩展,每个白点最多遍历到一次
if(j != now)
rt[GetHash(i, now)] = GetHash(i, j) + n * m;
if(arr[i][now + 1] == 2)
Right(now + 1, i, j);
}
void GetMin(int now) {//dfs求构造方式
vis[now] = true;
int SIZ = v[now].size();
for(int i = 0; i < SIZ; i++) {
int next = v[now][i].to;
if(vis[next] || !v[now][i].val)
continue;
GetMin(next);
}
}
int main() {
scanf("%d %d", &n, &m);
s = 0; t = 2 * n * m + 1;//源点和汇点初始化
char ch;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
cin >> ch;
if(ch == '1')
arr[i][j] = 1;
else
arr[i][j] = 2;
}
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(i == 1 && j == 1)
continue;
if(arr[i][j] == 1) {//向右或向下扩展,一个白点会被访问2次
Down(i, i, j);
Right(j, i, j);
}
}
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++) {
if(arr[i][j] == 1) {//源点到左部点,汇点到右部点连边
int now = GetHash(i, j);
int idnow = v[now].size();
int ids = v[s].size();
v[s].push_back(Node(now, 1, idnow));
v[now].push_back(Node(s, 0, ids));
now = GetHash(i, j) + n * m;
idnow = v[now].size();
int idt = v[t].size();
v[now].push_back(Node(t, 1, idt));
v[t].push_back(Node(now, 0, idnow));
}
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(i == 1 && j == 1)
continue;
if(arr[i][j] == 1)
continue;
int A = dn[GetHash(i, j)];//左部点到右部点连边
int B = rt[GetHash(i, j)];
int idA = v[A].size();
int idB = v[B].size();
v[A].push_back(Node(B, 1, idB));
v[B].push_back(Node(A, 0, idA));
}
}
printf("%d\n", Dinic());
GetMin(s);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(arr[i][j] == 2)
continue;
if(!vis[GetHash(i, j)])//打印答案
printf("%d %d DOLJE\n", i, j);
if(vis[GetHash(i, j) + n * m])
printf("%d %d DESNO\n", i, j);
}
}
return 0;
}
二分图最小点覆盖构造方案+König定理证明的更多相关文章
- UVA1194 Machine Schedule[二分图最小点覆盖]
题意翻译 有两台机器 A,B 分别有 n,m 种模式. 现在有 k 个任务.对于每个任务 i ,给定两个整数$ a_i\(和\) b_i$,表示如果该任务在 A上执行,需要设置模式为 \(a_i\): ...
- POJ2226 Muddy Fields(二分图最小点覆盖集)
题目给张R×C的地图,地图上*表示泥地..表示草地,问最少要几块宽1长任意木板才能盖住所有泥地,木板可以重合但不能盖住草地. 把所有行和列连续的泥地(可以放一块木板铺满的)看作点且行和列连续泥地分别作 ...
- POJ1325 Machine Schedule(二分图最小点覆盖集)
最小点覆盖集就是在一个有向图中选出最少的点集,使其覆盖所有的边. 二分图最小点覆盖集=二分图最大匹配(二分图最大边独立集) 这题A机器的n种模式作为X部的点,B机器的m种模式作为Y部的点: 每个任务就 ...
- hihoCoder #1127:二分图最小点覆盖和最大独立集
题目大意:求二分图最小点覆盖和最大独立集. 题目分析:如果选中一个点,那么与这个点相连的所有边都被覆盖,使所有边都被覆盖的最小点集称为最小点覆盖,它等于最大匹配:任意两个点之间都没有边相连的最大点集称 ...
- [POJ] 2226 Muddy Fields(二分图最小点覆盖)
题目地址:http://poj.org/problem?id=2226 二分图的题目关键在于建图.因为“*”的地方只有两种木板覆盖方式:水平或竖直,所以运用这种方式进行二分.首先按行排列,算出每个&q ...
- 二分图 最小点覆盖 poj 3041
题目链接:Asteroids - POJ 3041 - Virtual Judge https://vjudge.net/problem/POJ-3041 第一行输入一个n和一个m表示在n*n的网格 ...
- HihoCoder1127 二分图三·二分图最小点覆盖和最大独立集
二分图三·二分图最小点覆盖和最大独立集 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 在上次安排完相亲之后又过了挺长时间,大家好像都差不多见过面了.不过相亲这个事不是说 ...
- hdu 2236(二分图最小点覆盖+二分)
无题II Time Limit: 2000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- 四川第七届 D Vertex Cover(二分图最小点覆盖,二分匹配模板)
Vertex Cover frog has a graph with nn vertices v(1),v(2),…,v(n)v(1),v(2),…,v(n) and mm edges (v(a1), ...
随机推荐
- io流读写操作
/** * * DOC 将F盘下的test.jpg文件,读取后,再存到E盘下面. * * @param args * @throws Exception */ public static void m ...
- 关闭Linux - centos7的防火墙
关闭Centos7的防火墙 在每台虚拟机上分别执行以下指令: systemctl stop firewalld.service #停止firewall systemctl disable firewa ...
- 从零搭建一个IdentityServer——目录(更新中...)
从零搭建一个IdentityServer--项目搭建 从零搭建一个IdentityServer--集成Asp.net core Identity 从零搭建一个IdentityServer--初识Ope ...
- HDU - 6761 Minimum Index (字符串,Lyndon分解)
Minimum Index 题意 求字符串所有前缀的所有后缀表示中字典序最小的位置集合,最终转换为1112进制表示.比如aab,有三个前缀分别为a,aa,aab.其中a的后缀只有一个a,位置下标1:a ...
- FZU - 1901 Period II (kmp)
传送门:FZU - 1901 题意:给你个字符串,让你求有多少个p可以使S[i]==S[i+P] (0<=i<len-p-1). 题解:这个题是真的坑,一开始怎么都觉得自己不可能错,然后看 ...
- UVA-257 哈希算法
UVA-257 题意: 给你很多串,你需要找到这个串内有没有两个长度大于3的回文字符串,且要保证这两个回文字符串不相同,也不能完全覆盖,但可以重合一部分 题解: 首先判断回文的话可以通过马拉车算法(M ...
- hdu5414 CRB and String
Problem Description CRB has two strings s and t. In each step, CRB can select arbitrary character c ...
- Educational Codeforces Round 89 (Rated for Div. 2) A. Shovels and Swords (贪心)
题意:你有\(a\)个树枝和\(b\)个钻石,\(2\)个树枝和\(1\)个钻石能造一个铁铲,\(1\)个树枝和\(2\)个钻石能造一把剑,问最多能造多少铲子和剑. 题解:如果\(a\le b\),若 ...
- Intelligent IME HDU - 4287 字典树
题意: 给你m个字符串,每一个字符对应一个数字,如下: 2 : a, b, c 3 : d, e, f 4 : g, h, i 5 : j, k, l 6 : m, n, o ...
- 【cpp上】课后正误小题
State whether each of the following is true or false. If false, explain why. Assume the state ment u ...