题解:洛谷 AT_abc441_d Paid Walk
【题目来源】
洛谷:AT_abc441_d [ABC441D] Paid Walk - 洛谷
【题目描述】
有一个有向图(不一定简单),图中有 \(N\) 个顶点和 \(M\) 条边,顶点的编号为顶点 \(1, 2, \ldots, N\)。第
\(i\) 条 \((1\leq i\leq M)\) 边从顶点 \(U_i\) 到顶点 \(V_i\) ,代价为 \(C_i\) 。此外,每个顶点的出度最多为 \(4\)。
查找所有满足以下条件的顶点 \(v(1\leq v\leq N)\):
存在一条从顶点 \(1\) 到顶点 \(v\) 的路径,且同时满足以下两个条件:
- 它恰好遍历了 \(L\) 条边。如果同一条边被多次遍历,则每次遍历都计算在内。
- 已遍历边的代价总和至少为 \(S\),至多为 \(T\)。(如果同一条边被多次遍历,则每次遍历的代价都会加到总和中)。
【输入】
输入内容由标准输入法提供,格式如下
\(N\) \(M\) \(L\) \(S\) \(T\)
\(U_1\) \(V_1\) \(C_1\)
\(U_2\) \(V_2\) \(C_2\)
\(\vdots\)
\(U_M\) \(V_M\) \(C_M\)
【输出】
按升序输出满足条件的顶点,以空格分隔。
如果没有满足条件的顶点,则输出空行。
【输入样例】
5 8 3 80 100
1 2 20
1 3 70
2 1 30
2 5 10
3 2 10
3 4 30
3 5 20
5 1 70
【输出样例】
1 5
【核心思想】
-
问题分析:给定有向图,每个顶点出度最多为 \(4\)。求所有满足条件的顶点 \(v\):存在从 \(1\) 到 \(v\) 的路径,恰好经过 \(L\) 条边,且边权总和在 \([S, T]\) 范围内。这是一个深度优先搜索 + 剪枝问题,核心在于枚举所有长度为 \(L\) 的路径,检查其边权总和是否满足约束。
-
算法选择:
- DFS 枚举路径:从顶点 \(1\) 出发,递归遍历所有长度为 \(L\) 的路径,记录满足边权和约束的终点
- 剪枝优化:当前路径长度 \(> L\) 或当前边权和 \(> T\) 时提前终止搜索
-
关键步骤:
- 读入数据:\(N\)、\(M\)、\(L\)、\(S\)、\(T\),以及 \(M\) 条有向边 \((U_i, V_i, C_i)\)
- 链式前向星建图:用邻接表存储有向图
- DFS 搜索(从 \(u=1\)、\(l=0\)、\(val=0\) 开始):
- 若 \(l > L\) 或 \(val > T\):剪枝返回
- 若 \(l = L\):若 \(val \geq S\),标记 \(ans[u] = 1\),返回
- 遍历 \(u\) 的所有出边,递归搜索 \(dfs(v, l+1, val+w)\)
- 输出结果:按升序输出所有 \(ans[i] = 1\) 的顶点
-
时间/空间复杂度:
- 时间复杂度:\(O(4^L)\),每条路径长度最多 \(L\),每个节点出度最多 \(4\),最坏情况路径数为 \(4^L\)
- 空间复杂度:\(O(N + M + L)\),邻接表、递归栈深度 \(L\)
-
DFS 枚举与剪枝的核心思想:
- 路径长度的精确控制:DFS 的第三个参数 \(l\) 记录当前路径长度,确保只统计恰好 \(L\) 条边的路径,不多不少
- 边权和的双重约束:上界 \(T\) 用于剪枝(\(val > T\) 时提前返回),下界 \(S\) 用于终点判定(\(l = L\) 时检查 \(val \geq S\))
- 出度限制的意义:出度最多为 \(4\) 保证了 DFS 的分支因子有限,使得 \(4^L\) 的复杂度在 \(L\) 较小时可接受
- 重复访问允许:题目明确"同一条边被多次遍历每次计算",因此 DFS 无需标记访问状态,可以重复经过同一顶点
- 适用于路径枚举、精确长度约束、有向图搜索类问题
【算法标签】
普及 #DFS-图
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 200005, M = 200005;
int n, m, L, S, T;
int h[N], e[M], w[M], ne[M], idx, ans[N];
// 添加有向边
void add(int a, int b, int c)
{
e[idx] = b; // 边的终点
w[idx] = c; // 边的权重
ne[idx] = h[a]; // 下一条边的索引
h[a] = idx++; // 头指针指向新边
}
// 深度优先搜索
void dfs(int u, int l, int val)
{
if (l > L || val > T) return; // 剪枝:超过长度限制或值超过上限
if (l == L) // 达到指定长度L
{
if (val >= S) // 值满足下限要求
ans[u] = 1; // 标记节点u为可行终点
return;
}
// 遍历u的所有出边
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i]; // 邻居节点
dfs(j, l + 1, val + w[i]); // 递归搜索
}
}
int main()
{
// 输入
cin >> n >> m >> L >> S >> T;
// 初始化邻接表
memset(h, -1, sizeof(h));
// 读入m条边
for (int i = 1; i <= m; i++)
{
int u, v, w;
cin >> u >> v >> w;
add(u, v, w); // 添加有向边
}
// 从节点1开始DFS
dfs(1, 0, 0);
// 输出所有可行的终点节点
for (int i = 1; i <= n; i++)
if (ans[i])
cout << i << " ";
cout << endl;
return 0;
}
【运行结果】
5 8 3 80 100
1 2 20
1 3 70
2 1 30
2 5 10
3 2 10
3 4 30
3 5 20
5 1 70
1 5
浙公网安备 33010602011771号