最小路径覆盖
一. 定义
二.DAG(有向无环图)的最小不相交路径覆盖
三.DAG(有向无环图)的最小相交路径覆盖
四.题目
一.定义:
通俗点讲,就是在一个有向图中,找出最少的路径,使得这些路径经过了所有的点。
最小路径覆盖分为最小不相交路径覆盖和最小可相交路径覆盖。
最小不相交路径覆盖:每一条路径经过的顶点各不相同。
如图,其最小路径覆盖数为3。即1->3 > 4,2,5。
最小可相交路径覆盖:每一条路径经过的顶点可以相同。
如果其最小路径覆盖数为2。即1->3->4,2->3 > 5。
特别的,每个点自己也可以称为是路径覆盖,只不过路径的长度是0。
二.DAG(有向无环图)的最小不相交路径覆盖
1.算法:
把原图的每个点V拆成Vx和Vy两个点,如果有一条有向边A->B,
那么就加边Ax−>By。这样就得到了一个二分图。
那么最小路径覆盖=原图的结点数-新图的最大匹配数。
2.证明:
(1)一开始每个点都是独立的为一条路径,总共有n条不相交路径。
我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,
也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。
所以有最小路径覆盖=原图的结点数-新图的最大匹配数。
(2)因为路径之间不能有公共点,所以加的边之间也不能有公共点,这就是匹配的定义。
说的拆点这么高深,其实操作起来超级超级简单,甚至没有操作。
简单的来说,每个顶点都能当成二分图中作为起点的顶点。
(3)至于为什么答案是 n - ans(**)
用dfs求的ans表示的意思是匹配数,换句话说,就是一条路连着的路径上的点的个数 - 1
比如上图,我们用匈牙利算法,遍历每个点,发现1、3点可行,可匹配,而2、4、5都不能行。
如果我们单纯考虑1->3->5这条路径,有三个点,然后有两个点匹配了,我们用3减去2,就得到了1,这个1就是一个路径数,需要用1条路径来覆盖,再对剩下的2、4考虑,分别都需要一条路径来覆盖,因为2 - 0 = 0,2是顶点个数,0是匹配数。
所以,对于每条分路径,我们需要 v - cnt的路径来覆盖,最后将每个分路径合起来,我们得到n - cnt。
poj1422空袭
题目大意:有n个点和m条有向边,现在要在点上放一些伞兵,然后伞兵沿着图走,直到不能走为止
每条边只能是一个伞兵走过,问最少放多少个伞兵
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int maxn = 2e3 + 10;
int n, m, e; // m, n分别表示左、右侧集合的元素数量
vector<int>a[maxn]; //邻接矩阵存图
bool vis[maxn]; //记录右侧元素是否已被访问过
int match[maxn]; //记录当前右侧元素所对应的左侧元素
bool dfs(int pos){
for (int i = 0; i < a[pos].size(); ++i) { //选用vector变量则i不为右边点,v才为
int v = a[pos][i];
if (!vis[v]) { //有边且未访问
vis[v] = true; //记录状态为访问过
if (!match[v] || dfs(match[v])) {//如果暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
match[v] = pos;//当前左侧元素成为当前右侧元素的新匹配
return true;//返回匹配成功
}
}
}
return false; //循环结束,仍未找到匹配,返回匹配失败
}
int Hungarian() {
int ans = 0;
memset(match,0 , sizeof(match));
for (int i = 1; i <= n; i++){
memset(vis, false, sizeof(vis)); //重置vis数组
ans+=dfs(i);
}
return n - ans;
}
int main()
{
ios::sync_with_stdio(0);
int t;
cin >> t;
while (t--) {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
a[i].clear();
}
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
a[u].push_back(v);
}
cout << Hungarian() << '\n';
}
return 0;
}
三.DAG(有向无环图)的最小可相交路径覆盖
1.算法:
先用floyd求出原图的传递闭包,即如果a到b有路径,那么就加边a->b。
然后就转化成了最小不相交路径覆盖问题。
证明:
为了连通两个点,某条路径可能经过其它路径的中间点。比如1->3->4,2->4->5。
但是如果两个点a和b是连通的,只不过中间需要经过其它的点,
那么可以在这两个点之间加边,那么a就可以直达b,
不必经过中点的,那么就转化成了最小不相交路径覆盖。
poj2594寻宝(最小可相交路径覆盖)
题目大意:给你 1~N 个点, M 条有向边。问你最少需要多少个机器人,
让它们走完所有节点,不同的机器人可以走过同样的一条路,图保证为 DAG。
输入由带有两个零的单行终止。
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 5e2 + 10;
bool dis[N][N];
bool vis[N];
int match[N];
int n, m;
void floyd(int n) {
for (int k = 1; k <= n; ++k)
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (dis[i][k] && dis[k][j])//传递可达性
dis[i][j] = true;
}
bool dfs(int pos){
for (int i = 1; i <= n; i++) {
if (dis[pos][i] && !vis[i]) { //有边且未访问
vis[i] = true; //记录状态为访问过
if (!match[i] || dfs(match[i])) {//如果暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
match[i] = pos;//当前左侧元素成为当前右侧元素的新匹配
return true;//返回匹配成功
}
}
}
return false; //循环结束,仍未找到匹配,返回匹配失败
}
int solve(int n) {
int cnt = 0;
memset(match, 0, sizeof(match));
for (int i = 1; i <= n; ++i) {
memset(vis, 0, sizeof(vis));
cnt += dfs(i);
}
return n - cnt;
}
int main() {
while (cin>>n>>m, n + m) {
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
dis[i][j] = false;
for (int i = 1; i <= m; ++i) {
int a, b;
cin >> a >> b;
dis[a][b] = true;
}
floyd(n);
int ans = solve(n);
cout << ans << '\n';
}
return 0;
}
四.题目
四:题目
洛谷P2764 最小路径覆盖问题
## 题目描述
给定有向图 $G = (V, E)$ 。设 $P$ 是 $G$ 的一个简单路(顶点不相交)的集合。如果 $V$ 中每个定点恰好在 $P$ 的一条路上,则称 $P$ 是 $G$ 的一个路径覆盖。$P$ 中路径可以从 $V$ 的任何一个定点开始,长度也是任意的,特别地,可以为 $0$。$G$ 的最小路径覆盖是 $G$ 所含路径条数最少的路径覆盖。设计一个有效算法求一个 DAG(有向无环图)$G$ 的最小路径覆盖。
## 输入格式
第一行有两个正整数 $n$ 和 $m$。$n$ 是给定 DAG(有向无环图)$G$ 的顶点数,$m$ 是 $G$ 的边数。接下来的 $m$ 行,每行有两个正整数 $i$ 和 $j$ 表示一条有向边 $(i, j)$。
## 输出格式
从第一行开始,每行输出一条路径。文件的最后一行是最少路径数。
坑点
需要用scanf和printf才能通过(*******重点);
#include<stdio.h>
#include <cstring>
const int maxn = 2e4 + 10;
int n, m, cnt;
int head[maxn], match[maxn];
bool vis[maxn];
struct edge
{
int next, to;
}e[maxn]; //链式前向星存图
void insert(int x, int y)
{
e[++cnt].next = head[x];
e[cnt].to = y;
head[x] = cnt;
}
bool dfs(int pos)
{
for (int i = head[pos], t; i; i = e[i].next)
if (!vis[t = e[i].to]){
vis[t] = true;
if (!match[t] || dfs(match[t])){//如果暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
match[pos] = t, match[t] = pos;//当前左侧元素成为当前右侧元素的新匹配
return true;
}
}
return false;
}
int Hungarian() {
int sum = 0;
for (int i = 1; i <= n; i++){
memset(vis, false, sizeof(vis)); //重置vis数组
if (dfs(i))sum++;
}
return n- sum;
}
void Print(int x) //打印路径
{
x += n;
do
printf("%d ", x = x - n);
while (vis[x] = 1, x = match[x]);
printf("\n");
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1, x, y; i <= m; ++i) {
scanf("%d%d", &x, &y);
insert(x, y + n); //建立二分图左右边
}
int ans=Hungarian();
memset(vis, 0, sizeof vis);
for (int i = 1; i <= n; ++i)
if (!vis[i])
Print(i);
printf("%d\n", ans);
return 0;
}