给你一个 DAG,要你找最少数量的路径,使得一个点只会被一个路径经过,也一定会有路径经过它。
路径的长度可以是 0,即一个点也代表一个路径。
输出路径数量和每条路径的走法。
最小路径覆盖问题
题目大意
给你一个 DAG,要你找最少数量的路径,使得一个点只会被一个路径经过,也一定会有路径经过它。
路径的长度可以是 0,即一个点也代表一个路径。
输出路径数量和每条路径的走法。
思路
首先你知道求路径数量就是直接拆点然后网络流就好了。
至于问什么,这里就简单说说:
大概是一开始所有点都是一个路径,然后你加你给出的路径就相当于可以把给出的路径相连的路径合并成一个路径。
然后路径又不能有公共点,那新加的边就不能有公共点,你就会发现它其实就是在进行匹配。
那你最多匹配了 x 对,原来有 n 个点,那匹配一次两个路径边一个,减少一,那最小路径覆盖就是 n−x 咯。(x 就是我们二分图的最大匹配)
那建图就是把一个点 i 拆成 ix,iy 两个点,分别在二分图的左边右边。
然后 i 连向 j 就相当于二分图中 ix→jy 这条边。
但其实这道题最烦的应该是如何输出每条路径。
考虑到 n 很小,n2 都可以过,我们考虑暴力跳。
那你网络流要流的时候就记录从这个点开始留到哪个点(其实就是这个点跟右边的哪个点匹配),然后再记录右边的那些点是否能被留到。
如果一个右边的点不能被留到,就说明它应该是一个起点,就从它左边对于的点开始流,流到右边,再跑到它对应左边的点。一直这样跳下去,知道你流不到点或留到的不是右边的点,中途我们跳到的带点对应原来的点就是我们要顺序输出的。
然后就好了。
不过这题的 SPJ 比较神奇,你搞的时候一定要注意跑到它对应左边的点要判断是否是右边的点再太跳,而且这个判断一定要比较严谨。不然的话不开 O2 可以过,开了 O2 就 RE+TLE,LOJ 可以选开不开,但洛谷的这道题是一定会开的。
代码
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#define INF 1000000
using namespace std;
struct node {
int x, to, nxt, op;
}e[200001];
int n, m, x, y, S, T, ans, dis[20001];
int le[20001], lee[20001], KK, gogo[20001];
bool ru[20001];
queue <int> q;
void add(int x, int y, int z) {
e[++KK] = (node){z, y, le[x], KK + 1}; le[x] = KK;
e[++KK] = (node){0, x, le[y], KK - 1}; le[y] = KK;
}
int get_num(int x, int op) {
return op * n + x;
}
bool bfs() {
for (int i = 1; i <= T; i++) {
dis[i] = -1;
lee[i] = le[i];
}
while (!q.empty()) q.pop();
q.push(S);
dis[S] = 0;
while (!q.empty()) {
int now = q.front();
q.pop();
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].x > 0 && dis[e[i].to] == -1) {
dis[e[i].to] = dis[now] + 1;
q.push(e[i].to);
}
}
return dis[T] != -1;
}
int dfs(int now, int sum) {
if (now == T) return sum;
int go = 0;
for (int &i = lee[now]; i; i = e[i].nxt)
if (e[i].x > 0 && dis[e[i].to] == dis[now] + 1) {
int this_go = dfs(e[i].to, min(sum - go, e[i].x));
if (this_go) {
gogo[now] = e[i].to;
if (e[i].to > n && e[i].to <= 2 * n) ru[e[i].to - n] = 1;
e[i].x -= this_go;
e[e[i].op].x += this_go;
go += this_go;
if (go == sum) return go;
}
}
if (!go) dis[now] = -1;
return go;
}
void dinic() {
while (bfs())
ans -= dfs(S, INF);
}
int read() {
int re = 0, zf = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') zf = -zf;
c = getchar();
}
while (c >= '0' && c <= '9') {
re = (re << 3) + (re << 1) + c - '0';
c = getchar();
}
return re * zf;
}
int main() {
n = read();
m = read();
for (int i = 1; i <= m; i++) {
x = read();
y = read();
add(get_num(x, 0), get_num(y, 1), 1);
}
S = 2 * n + 1;
T = 2 * n + 2;
for (int i = 1; i <= n; i++) {
add(S, get_num(i, 0), 1);
add(get_num(i, 1), T, 1);
}
ans = n;
dinic();
for (int i = 1; i <= n; i++)
if (!ru[i]) {
printf("%d", i);
int now = i;
while (gogo[now] > n && gogo[now] <= 2 * n) {
now = gogo[now] - n;
printf(" %d", now);
}
printf("\n");
}
printf("%d\n", ans);
return 0;
}
__EOF__
【推荐】国内首个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岁的心里话
· 按钮权限的设计及实现