题解-luogu-p3953逛公园

题目链接

给定一个$n$个点$m$条边的有向带权图,设起点到终点的最短路为$d$,求起点到终点满足权值总和小于等于$(d+k)$的路径数量

$1 \le p\le 10^9 $ , $1 \le n\le 10^5$ , $1 \le m\le 2 \times 10^5$ , $1 \le k\le 50$

感谢@oy 的贡献

一道看似图论实则可以用动态规划解决的题目。

朴素的状态定义:$f[u][k]$表示从$1$号节点走到$u$号节点,路径长度为$k$的方案总数。状态转移方程:
$$
f[u][k]=\sum_{(u,v)\in E}f[v][k-w(u,v)]
$$
但是这样的状态定义有一个严重的问题:空间消耗过大。考虑到题目中给出的$K$值并不大,我们可以利用题目所要求的信息来优化状态设计。

优化后的状态定义:$d[u]$表示从$1$号节点走到$u$号节点的最短路长度,$f[u][k]$表示从$1$号节点走到$u$号节点,路径长度为$d[u]+k$的方案总数

如此一来,状态所需的空间大大减少,但相应的状态转移略显复杂。不妨设$f[u][k]$状态可以由$f[v][x]$转移得到,则:
$$
d[v]+x+w(u,v)=d[u]+k
$$
移项,得到:
$$
x=d[u]-d[v]+k-w(u,v)
$$
因此,完整的状态转移方程如下:
$$
f[u][k]=\sum_{(u,v)\in E}f[v][d[u]-d[v]+k-w(u,v)]
$$
我们最终要求的答案即为$\sum_{i=0}^Kf[N][i]$,用记忆化搜索实现即可。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// luogu-judger-enable-o2
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100001;

struct Edge {int v, w;};
vector<Edge> G1[MAXN], G2[MAXN];

int T, N, M, K, P;
bool fail;
int f[MAXN][51];
int d[MAXN];
bool vis[MAXN];
bool ins[MAXN][51];

void Dijkstra() {
memset(vis, 0, sizeof(vis));
memset(d, 0x3f, sizeof(d));
priority_queue<pair<int, int> > q;
q.push(make_pair(0, 1));
d[1] = 0;
while (q.size()) {
int u = q.top().second;
q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (vector<Edge>::iterator it = G1[u].begin(); it != G1[u].end(); it++) {
int v = it->v, w = it->w;
if (d[u] + w < d[v]) {
d[v] = d[u] + w;
q.push(make_pair(-d[v], v));
}
}
}
}

inline int dp(int u, int k) {
if (k < 0) return 0;
if (ins[u][k]){
fail = 1;
return 0;
}
if (f[u][k]) return f[u][k];
ins[u][k] = 1;
int ans = 0;
for (vector<Edge>::iterator it = G2[u].begin(); it != G2[u].end(); it++) {
int p = it->v, w = it->w;
ans = (ans + dp(p, d[u] - d[p] + k - w))%P;
if (fail == 1) return 0;
}
ins[u][k] = 0;
return f[u][k] = ans;
}

int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%d%d%d", &N, &M, &K, &P);
for (register int i = 1; i <= M; ++i) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
G1[u].push_back((Edge){v, w});
G2[v].push_back((Edge){u, w});
}
Dijkstra();
dp(1, 0);
f[1][0] = 1;
int ans = 0;
for (register int i = 0; i <= K; ++i) {
ans = (ans + dp(N, i))%P;
}
if(fail == 1) puts("-1");
else printf("%d\n", ans);
fail = 0;
memset(f, 0, sizeof(f));
memset(ins, 0, sizeof(ins));
for (register int i = 1; i <= N; ++i) {
G1[i].clear(), G2[i].clear();
}
}
}

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×