「Luogu P3953」[NOIP2017 提高组] 逛公园
题目
策策同学特别喜欢逛公园。公园可以看成一张 \(N\) 个点 \(M\) 条边构成的有向图,且没有 自环和重边。其中 \(1\) 号点是公园的入口,\(N\) 号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。
策策每天都会去逛公园,他总是从 \(1\) 号点进去,从 \(N\) 号点出来。
策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果 \(1\) 号点到 \(N\) 号点的最短路长为 \(d\),那么策策只会喜欢长度不超过 \(d + K\) 的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?
为避免输出过大,答案对 \(P\) 取模。
如果有无穷多条合法的路线,请输出 \(-1\)。
分析
这题看上去像是 DP. 记 \(f[i][j]\) 表示从 \(1\) 号点到 \(i\) 号点, 路径长度为 \(j\) 的方案数. 不过这显然存不下. 不难发现 \(j\) 只可能取 \([\mathrm{dis}_{i}, \mathrm{dis}_{i}+k]\) 这个区间的值, 而区间长度为 \(k+1\leq 51\) 很小, 因此我们可以先跑一遍 Dijkstra 然后再令 \(f[i][j]\) 表示从 \(1\) 号点到 \(i\) 号点, 路径长度为 \(j+\mathrm{dis}_{i}\) 的方案数.
以什么顺序遍历所有结点? 在 dfs 的过程中进行状态转移. 假设当前有 \((u, v, w)\) 这条边, 正在枚举的 \(u\) 的路径长度为 \(\mathrm{dis}_{u}+j\), 那么状态转移方程为 \(f[v][j+\mathrm{dis}_{u}+w-\mathrm{dis}_{v}] = f[v][j+\mathrm{dis}_{u}+w-\mathrm{dis}_{v}] + f[u][j]\), 也就是说, 我们更新的答案是还未访问的 \(f[v][j+\mathrm{dis}_{u}+w-\mathrm{dis}_{v}]\), 同时 \(f[u][j]\) 也不是完整的, 我们希望用 \(v\) 的答案来更新 \(u\), 那就建一个反图, 在反图上 dfs 更新答案即可, 此时对于原图中的边 \((u, v, w)\), 我们有总的状态转移方程:
一个结点会被多次遍历到怎么办? 不从不同的路径到达怎么统计路径方案数嘛.
遇到环了怎么办? 用一个栈存储当前遍历的路径结点, in[] 数组判断是否在栈中就能判环了 qwq (不过我们只需要用到 in[] 的信息不需要用到栈)
如果是零环怎么办?此时再次回到结点 \(u\) 时枚举的路径长度仍然是 \(\mathrm{dis}_{u}+j\), 那么我们给 in 数组再加一维, 用 in[u][j] 判定之前 dfs 栈信息即可, 如果有零环则直接返回 -1, 答案显然是 \(\infty\).
代码
#include <bits/stdc++.h>
using PII = std::pair<int, int>;
constexpr int N = 1e5 + 5;
constexpr int M = 2e5 + 5;
constexpr int INF = 0x3f3f3f3f;
int n, m, k, p;
std::vector<PII> e1[N], e2[N];
int dis[N], f[N][55];
bool flag;
bool vis[N], in[N][55];
void insert(int u, int v, int w) {
e1[u].push_back({v, w}), e2[v].push_back({u, w});
}
void dijkstra(int s) {
for (int i = 1; i <= n; ++i) dis[i] = INF, vis[i] = 0;
std::priority_queue<PII> q;
q.push({0, s});
dis[s] = 0;
while (!q.empty()) {
int u = q.top().second;
q.pop();
if (vis[u]) continue;
for (auto it : e1[u]) {
int v = it.first, w = it.second;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
q.push({-dis[v], v});
}
}
}
}
int dfs(int u, int len) {
if (len > k || len < 0) {
return 0;
}
if (in[u][len]) {
flag = 1;
return -1;
}
if (f[u][len]) {
return f[u][len];
}
in[u][len] = 1;
int sum = 0;
for (auto it : e2[u]) {
int v = it.first, w = it.second;
sum = (sum + dfs(v, len + dis[u] - dis[v] - w)) % p;
if (flag) {
return -1;
}
}
in[u][len] = 0;
if (u == 1 && len == 0) sum++; // 别忘了边界情况
return f[u][len] = sum;
}
void solve() {
std::cin >> n >> m >> k >> p;
for (int i = 1; i <= n; ++i) {
e1[i].clear(), e2[i].clear();
}
for (int i = 1, u, v, w; i <= m; ++i) {
std::cin >> u >> v >> w;
insert(u, v, w);
}
dijkstra(1);
memset(f, 0, sizeof(f));
memset(in, 0, sizeof(in));
int ans = 0;
flag = 0;
for (int i = 0; i <= k; ++i) {
int tmp = dfs(n, i);
if (flag) {
std::cout << "-1\n";
return;
} else {
ans = (ans + tmp) % p;
}
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}

浙公网安备 33010602011771号