这是一道京东笔试题目,属于动态规划中等难度题目
题目描述
第一行三个整数n,m,a(1≤n≤100,1≤m≤1000,1≤a≤1000),含义如题面所示。 接下来m行,第i行三个整数 u i u_i ui, v i v_i vi, w i ( 1 ≤ u i , v i ≤ n , 1 ≤ w i ≤ a ) w_i(1 ≤ u_i,v_i \le n,1≤ w_i ≤ a) wi(1≤ui,vi≤n,1≤wi≤a),描述了一条道路。
输出描述
如果牛牛回家的方家数大于等于 20220201种,请你在第一行输出All roads lead to Home!,然后在第二行输出回家的方案数对 20220201 取模的结里
否则只需要输出一行一个整数,表示牛牛回家的方案数。
样例
输入
3 6 2
1 2 1
1 2 1
1 2 1
2 3 1
2 3 1
2 3 1
输出
9
说明
从城市一到城市二有3种不同的走法,从城市二到城市三也有3种不同的走法,根据乘法原理我们可以知道,一共有3x3=9种不同的回家方法。
题解
状态:
- dp[i][j]表示从1到i花费j的方案数量
- st[i][j]表示从1到i花费为j的方案数量是否超出20220201
转移方程:
dp[u][i + w] = (dp[u][i + w] + dp[v][i]) % INF;
其中更新路径由v到u,i是终点为v的花费
难点是为什么要把for (int i = 0; i < a; ++i) 写在外层?
假如写在内层,那就是算完一个节点的状态再算下一个节点的状态,同时已经算出的节点状态不会再次更新,但是如果题目的路径存在环,就导致节点状态会被反复更新,通过循环的方式不行,采用拓扑的遍历的方式同样也不行,因为拓扑遍历不能有环。
所以必须写在外层,写在外层为什么可以?写在外层是按花费更新的,花费每增加1就更新所有节点,这样必须保证如果是v更新u:
- 那么v的状态一定是最终的正确状态,以后不会被其他节点再次更新
- u被更新后不能在本次花费下作为v去更新其他节点
第一个条件是满足的,先看初始状态,dp[1][0]=1,只有1号节点初始花费为0有一种方案,其他节点花费为0的情况下是0。每个花费只能由花费小的来更新,所以如果遍历到m,那么所有节点小于m的花费状态一定作为v去更新了,那么m就是最终状态。
由于边权>0,所以dp[v][i]更新dp[u][j],j>i,那dp[v][j]不可能在本次花费下去更新别的节点了,因为本次花费是i。
然后就是如果最终的答案大于20220201就输出“所有路线均可”,由于计算的过程涉及取模,所以还需要定义一个st[i][j]表示从1到i花费为j的方案数量是否超出20220201,如果v更新了u,那么v是否超出模数的状态就会继承到u,这个还是比较好想的。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
const int INF = 20220201;
using namespace std;
int main() {
int n, m, a;
cin >> n >> m >> a;
vector<vector<pair<int, int>>> graph(n + 1);
for (int i = 0; i < m; ++i) {
int vi, ui, wi;
cin >> vi >> ui >> wi;
graph[vi].emplace_back(ui, wi);
}
vector<vector<int>> dp(n + 1, vector<int>(a + 1, 0));
vector<vector<bool>> st(n + 1, vector<bool>(a + 1, 0));
dp[1][0] = 1;
for (int i = 0; i < a; ++i) {
for (int v = 1; v <= n; ++v) {
if (dp[v][i] > 0 || st[v][i]) {
for (auto &[u, w] : graph[v]) {
if (i + w <= a) {
dp[u][i + w] = (dp[u][i + w] + dp[v][i]) % INF;
if(dp[u][i + w] + dp[v][i] >= INF) st[u][i + w] = true;
if(st[v][i]) st[u][i + w] = true;
}
}
}
}
}
if(st[n][a]) cout << "All roads lead to Home!" << endl;
cout << dp[n][a] << endl;
return 0;
}