题目大意:
解题思路:
首先我们先明确为什么会出现无穷多条路径
- 这个答案显而易见,就是有0环
那么怎样的0环会使得答案出现无穷多条路径呢
- 对于
k
=
1
k=1
k=1的情况,作如下图:
- 因为最短路为3, k = 1 k=1 k=1,所以可行的路径大小在 [ 3 , 4 ] [3,4] [3,4],那么处于下方的0环就不在我们考虑的范围内,而上方的0环则会使我们的路径有无限多条
- 所以当且仅当0边处在可走区间的范围内时,这些0边构成的0环才会对结果产生影响,同时我们把这些0边又叫做可行0边
那么如何把得到所有的可行边,再把这些可行边重新建图(注意是可行边,不单单指可行0边)
- 此处借由大佬博客的一句话:
判断是否可行的方法为如果某条边连接的两个端点分别到起点和终点的距离加上此条边的长度小于等于起点到终点的最短路+k,说明通过该条路径存在答案,并判断其是否在以1为源的最短路上,将其加入到新图(即最短路边集) - 这样子既能保证所有可行的0边加入到新图(因为所有边权非负,所以可行的0边连接的两端恰好满足以上两个条件),又能保证在该图上跑dp时不会使最短路有增量(此处后面会有解释)
- 上图就会变成:
- 接下来只要利用拓扑排序判环即可(想想为什么如果有环一定是0环,再看看上面两个判断条件)
接下来就是dp在分层图上求解答案的过程
- 设 d p [ i ] [ j ] dp[i][j] dp[i][j]代表 1 1 1到 i i i结点增量为 j j j的所有可行边
- 设想有 k + 1 k+1 k+1层图,那么每一层得到的解即代表不同增量下的解,最后算总和即可
- 而如何在某一层跑dp时不会影响到其他层呢(疯狂暗示,看看上面),其实只要在一开始建的最短路边集上跑就行啦
- 那么层与层之间是否毫无关联呢,显然不是呀!!!
如果毫无关联的话,那不就相当于k始终为0,k显然不会始终为0 - 那么每一层之间的关系,就由那些没有加入最短路边集的边来联系,因为跑它们,会有增量,所以就能用来联系每一层
然后,每条边的增量又要重新计算(可看下面代码) - 最后累加dp[n][0~k]即可
AC代码
#include <bits/stdc++.h>
#define inf 1000000000
using namespace std;
const int maxn = 2e5 + 10;
int T, n, m, k, p;
int a[maxn], b[maxn], w[maxn], f[maxn], g[maxn], sumin[maxn], tuopu[maxn];
int dp[maxn][60];
bool vis[maxn];
struct edge {
int u, w;
friend bool operator < (edge A, edge B) {
return A.w > B.w;
}
};
vector <edge> G[maxn];
void init() {
for (int i = 1; i <= n; i++)
for (int j = 0; j <= k; j++)
dp[i][j] = 0;
}
void clearedge() {
for (int i = 1; i <= n; i++) G[i].clear();
//for (int i = 1; i <= n; i++) sumin[i] = 0;
memset(sumin, 0, sizeof(sumin));
// memset(tuopu, 0, sizeof(tuopu)); //why?不是已经初始化了吗
}
priority_queue <edge> que1;
void dijstra(int s, int *d) {
for (int i = 1; i <= n; i++) d[i] = inf, vis[i] = false;
d[s] = 0;
que1.push({s, 0});
while (!que1.empty()) {
edge tt = que1.top(); que1.pop();
int u = tt.u, w = tt.w;
if (vis[u]) continue;
vis[u] = true;
for (auto np : G[u]) {
if (d[np.u] > d[u] + np.w) {
d[np.u] = d[u] + np.w;
que1.push({np.u, d[np.u]});
}
}
}
}
queue <int> que2;
int cnt;
void Tuopu() {
cnt = 0;
que2.push(1);
while (!que2.empty()) {
int u = que2.front(); que2.pop();
tuopu[++cnt] = u;
for (auto np : G[u]) {
sumin[np.u]--;
if (!sumin[np.u]) que2.push(np.u);
}
}
}
int main() {
//freopen("in.txt", "r", stdin);
cin >> T;
while (T--) {
cin >> n >> m >> k >> p;
init();
clearedge();
for (int i = 1; i <= m; i++) {
cin >> a[i] >> b[i] >> w[i];
G[a[i]].push_back({b[i], w[i]});
}
dijstra(1, f);
clearedge();
for (int i = 1; i <= m; i++)
G[b[i]].push_back({a[i], w[i]});
dijstra(n, g);
clearedge();
for (int i = 1; i <= m; i++) {
if (f[a[i]] + w[i] + g[b[i]] <= f[n] + k && f[a[i]] + w[i] == f[b[i]])
G[a[i]].push_back({b[i], 0}), sumin[b[i]]++;
}
Tuopu();
//计算增量
for (int i = 1; i <= m; i++) w[i] = f[a[i]] + w[i] - f[b[i]];
bool flag = true;
for (int i = 1; i <= n; i++) if (sumin[i]) flag = false;
if (!flag) {
cout << -1 << endl;
continue;
}
dp[1][0] = 1;
for (int j = 0; j <= k; j++) {
for (int i = 1; i <= cnt; i++) {
//按照拓扑序,cnt不一定等于n,因为某些点可能不会被加入进去
int now = tuopu[i];
for (auto np : G[now])
dp[np.u][j] = (dp[np.u][j] + dp[now][j]) % p;
}
for (int i = 1; i <= m; i++)
if (j + w[i] <= k && w[i])
dp[b[i]][j + w[i]] = (dp[b[i]][j + w[i]] + dp[a[i]][j]) % p;
}
int ans = 0;
for (int i = 0; i <= k; i++)
ans = (ans + dp[n][i]) % p;
cout << ans << endl;
}
}