最小路径覆盖问题求解及与最小边覆盖的区别

来源:互联网 发布:极客头条php 编辑:程序博客网 时间:2024/03/29 19:20

最小边覆盖

边覆盖集:通俗地讲,所谓边覆盖集,就是G中所有的顶点都是E*中某条边的邻接顶点(边覆盖顶点),一条边只能覆盖2个顶点。


注意:在无向图中存在用尽量少的边去“覆盖”住所有的顶点(注意:单独一个点没有与它相连的边,也算作一次边去覆盖这个点),所以边覆盖集有极小与最小的区别。
极小边覆盖:若边覆盖E*中的任何真子集都不是边覆盖集,则称E*是极小边覆盖集。

最小边覆盖:边数最小的边覆盖集称为最小边覆盖,通俗地讲,就是极小边覆盖中的最小的一个集合。


最小边覆盖在二分图中的应用:最小边覆盖 = 最大独立集 = n - 最大匹配,这个是二分图上的一个性质。
两个很小的点:必须要求原图为二分图;边覆盖集中可能有多条边覆盖同一个顶点,其实可以这条边说是为了覆盖另一个顶点而存在的。

最小路径覆盖

用尽量少的不相互交叉简单路径覆盖有向无环图G的所有结点(不交叉指的是原图,而非后来构造的二分图)。即覆盖点(同样地,在路径覆盖中单独一个点没有与它相连的边,也算作一次路径去覆盖这个点)。建立一个二分图模型,把所有顶点i拆成两个:X集中的i和Y集中的i',如果有边i->j,则在二分图中引入边i->j',结果就是最小路径覆盖 = N - 最大匹配数。(N为原图中结点数)

最小路径覆盖和最小边覆盖不同,不要求给的图是二分图,而是要求是N×N的有向图,且不能有环,然后根据原图构造二分图,构造方法是将点一分为二,如:i分为i`和i``然后如果i和j有边,那么就在i`和j``之间连一条边。由此构成二分图。

然后最小路径覆盖 = n-m,n为原图的点的个数,m为新造二分图的最大匹配。证明也是特别简单的,根据定义最小路径覆盖里要求同一个点只可以属于一条路径,即路径是不可以开叉的,如果在二分图里选两条有公共点的边那么反应在原图上就是路径有岔路了,所以二分图里选的边必须是无公共交点的,这就是转化到最大匹配了。
(图片来源)
上图中,对应左边的DAG建立构造右边的二分图,可以找到二分图的一个最大匹配M:1-3',3-4',那么M中的这两条匹配边怎样对应DAG中的路径的边?
使二分图中一条边对应DAG中的一条有向边,1-3'对应DAG图中的有向边1->3,这样DAG中1就会有一个后继顶点(3会是1的唯一后继,因为二分图中一个顶点至多关联一条边!),所以1不会成为DAG中一条路径中的结尾顶点,同样,3-4'对应DAG中3->4,3也不会成为结尾顶点,那么原图中总共4个顶点,减去2个有后继的顶点,就剩下没有后继的顶点,即DAG路径的结尾顶点,而每个结尾顶点正好对应DAG中的一条路径,二分图中寻找最大匹配M,就是找到了对应DAG中的非路径结尾顶点的最大数目,那么原图DAG中顶点数-|M|就是DAG中结尾顶点的最小数目,即DAG的最小路径覆盖数.即上图中找到的最小路径覆盖集合为2, 1->3->4。

若是题目要求可多次经过一个点的最小路径覆盖呢?如下图:

(图片来源)

此时构造二分图,得到的匹配答案是2,最小路径覆盖答案便是5-2=3。而对于可多次经过同一点来说正确答案明显是2。

问题究竟出在哪里呢?其实就和这个交点2有关。既然边有相交,那么他们的连通性也应该连通下去。

解决的办法是对原图进行一次闭包传递(通过Warshell传递闭包算法),于是便增加了四条边:1->3, 1->5, 4->3, 4->5,然后再去求最小路径覆盖。

传递闭包概念:

一个有n个顶点的有向图的传递闭包为:有向图中的初始路径可达情况我们存入邻接矩阵A,邻接矩阵中A[i,j]表示i到j是否直接可达,若直接可达,则A[i,j]记为1,否则记为0;有向图中i到j有路径表示从i点开始经过其他点(或者不经过其他点)能够到达j点,如果i到j有路径,则将T[i,j]设置为1,否则设置为0;有向图的传递闭包表示从邻接矩阵A出发,求的是所有节点间的路径可达情况,该T矩阵就为所要求的传递闭包矩阵。

这时再求最大匹配数,匹配答案便是3,最小路径覆盖值为2,这是正确答案!


最小路径覆盖例题1(POJ-1422):

题意:一个镇里所有的路都是单向路且不会组成回路。派一些伞兵去那个镇里,要能够到达所有的路口,且超过一个 伞兵访问不交叉的路口(visits no intersection),其实题目应该是想表达伞兵访问路径时不能相互交叉,每个路口只能访问一次。每个在一个路口着陆了的伞兵可以沿着街去到其他路口。我们的任务是求出去执行任务的伞兵最少可以是多少个。

代码:

#include <algorithm>#include <string.h>#include <cstdio>using namespace std;const int maxn = 250;const int maxm = maxn*maxn;const int BAS = 120;struct node{int v, next;} edge[maxm];int no, head[maxn];int n, m, ans;int match[maxn], vis[maxn];inline void init(){no = 0; ans = 0;memset(head, -1, sizeof head);memset(match, -1, sizeof match);}inline void add(int u, int v){edge[no].v = v;edge[no].next = head[u];head[u] = no++;}int dfs(int cur){for(int k = head[cur]; k != -1; k = edge[k].next){if(vis[edge[k].v]) continue;vis[edge[k].v] = 1;if(match[edge[k].v] == -1 || dfs(match[edge[k].v])){match[edge[k].v] = cur;return 1;}} return 0;}int main(){int t, u, v;scanf("%d", &t);while(t--){scanf("%d %d", &n, &m); init();for(int i = 1; i <= m; ++i){scanf("%d %d", &u, &v);add(u, BAS+v);}for(int i = 1; i <= n; ++i){memset(vis, 0, sizeof vis);if(dfs(i)) ++ans;}printf("%d\n", n-ans);}return 0;}

最小路径覆盖例题2(POJ-2594):

题意:给一个有向无环图,有n个点,m条有向边。一个机器人可以从任意点开始沿着边的方向走下去。对于每一个机器人:走过的点不能再走过。对于每个点可被多个机器人走。问你最少用几个机器人可以走完所有的n个点,不同的机器人可以走相同的点(two different robots may contain some same point)。

分析:路径可以相互交叉,所以就是最小路径覆盖+Warshell传递闭包。

代码:

#include <string.h>#include <cstdio>using namespace std;const int maxn = 505;bool G[maxn][maxn];int match[maxn], vis[maxn]; int n, m;void Warshell(){//进行求解最小路径覆盖问题前提是有向无环图//所以不会出现传递闭包形成G[i][i]=1的情况 for(int i = 1; i <= n; ++i)for(int j = 1; j <= n; ++j)if(!G[i][j])for(int k = 1; k <= n; ++k)if(G[i][k] && G[k][j]){G[i][j] = 1;break;}}int dfs(int cur){for(int i = 1; i <= n; ++i){if(!G[cur][i] || vis[i]) continue;vis[i] = 1;if(match[i] == -1 || dfs(match[i])){match[i] = cur;return 1;}}return 0;}int main(){int u, v, ans;while(scanf("%d %d", &n, &m) && (n || m)){memset(G, 0, sizeof G);memset(match, -1, sizeof match);for(int i = 1; i <= m; ++i){scanf("%d %d", &u, &v);G[u][v] = 1;}Warshell(); ans = 0;for(int i = 1; i <= n; ++i){memset(vis, 0, sizeof vis);if(dfs(i)) ++ans;}printf("%d\n", n-ans);}return 0;}

继续加油~