有向无环图(DAG)的最小路径覆盖
DAG的最小路径覆盖
定义:在一个有向图中,找出最少的路径,使得这些路径经过了所有的点。
最小路径覆盖分为最小不相交路径覆盖和最小可相交路径覆盖。
最小不相交路径覆盖:每一条路径经过的顶点各不相同。如图,其最小路径覆盖数为3。即1->3>4,2,5。
最小可相交路径覆盖:每一条路径经过的顶点可以相同。如果其最小路径覆盖数为2。即1->3->4,2->3>5。
特别的,每个点自己也可以称为是路径覆盖,只不过路径的长度是0。
DAG的最小不相交路径覆盖
算法:把原图的每个点V拆成和两个点,如果有一条有向边A->B,那么就加边。这样就得到了一个二分图。那么最小路径覆盖=原图的结点数-新图的最大匹配数。
证明:一开始每个点都是独立的为一条路径,总共有n条不相交路径。我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数。
因为路径之间不能有公共点,所以加的边之间也不能有公共点,这就是匹配的定义。
习题:POJ1422
// // main.cpp // POJ1422最小不想交路径覆盖 // // Created by beMaster on 16/4/8. // Copyright © 2016年 beMaster. All rights reserved. // #include <iostream> #include <stdio.h> #include <string.h> #include <vector> using namespace std; const int N = 200 + 10; vector<int> g[N]; int cy[N]; bool vis[N]; bool dfs(int u){ for(int i=0; i<g[u].size(); ++i){ int v = g[u][i]; if(vis[v]) continue; vis[v] = true; if(cy[v]==-1 || dfs(cy[v])){ cy[v] = u; return true; } } return false; } int solve(int n){ int ret = 0; memset(cy, -1, sizeof(cy)); for(int i=1;i<=n;++i){ memset(vis, 0, sizeof(vis)); ret += dfs(i); } return n - ret; } int main(int argc, const char * argv[]) { int t,n,m; int u,v; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) g[i].clear(); for(int i=0;i<m;++i){ scanf("%d%d",&u,&v); g[u].push_back(v); } int ans = solve(n); printf("%d\n",ans); } return 0; }
DAG的最小可相交路径覆盖
算法:先用floyd求出原图的传递闭包,即如果a到b有路径,那么就加边a->b。然后就转化成了最小不相交路径覆盖问题。
证明:为了连通两个点,某条路径可能经过其它路径的中间点。比如1->3->4,2->4->5。但是如果两个点a和b是连通的,只不过中间需要经过其它的点,那么可以在这两个点之间加边,那么a就可以直达b,不必经过中点的,那么就转化成了最小不相交路径覆盖。
题目:POJ2594
// // main.cpp // POJ2594最小可相交路径覆盖 // // Created by beMaster on 16/4/8. // Copyright © 2016年 beMaster. All rights reserved. // #include <iostream> #include <stdio.h> #include <string.h> #include <vector> using namespace std; const int N = 500 + 10; bool dis[N][N]; bool vis[N]; int cy[N]; void floyd(int n){ for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) for(int k=1;k<=n;++k) if(dis[i][k] && dis[k][j])//传递可达性 dis[i][j] = true; } bool dfs(int u, int n){ for(int i=1;i<=n;++i){ if(!vis[i] && dis[u][i]){ vis[i] = true; if(cy[i]==-1 || dfs(cy[i], n)){ cy[i] = u; return true; } } } return false; } int solve(int n){ int cnt = 0; memset(cy,-1,sizeof(cy)); for(int i=1;i<=n;++i){ memset(vis,0,sizeof(vis)); cnt += dfs(i, n); } return n - cnt; } int main(int argc, const char * argv[]) { int n,m; int a,b; while(scanf("%d%d",&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){ scanf("%d%d",&a,&b); dis[a][b] = true; } floyd(n); int ans = solve(n); printf("%d\n",ans); } return 0; }
参考:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现