传送门
先考虑没有
0
0
0 边的情况
求出 1 到
n
n
n 的最短路,用
f
i
,
j
f_{i,j}
fi,j 表示到当前点,比最短路多
j
j
j 的方案数
f
1
,
0
=
1
f_{1,0}=1
f1,0=1,
f
u
,
j
=
f
v
,
j
−
(
d
i
s
[
v
]
+
w
[
i
]
−
d
i
s
[
u
]
)
f_{u,j}=f_{v,j-(dis[v]+w[i]-dis[u])}
fu,j=fv,j−(dis[v]+w[i]−dis[u])
因为这并不是一个
d
a
g
dag
dag,而是一个存在环的有向图
从
n
n
n 开始建反图,记忆化搜索即可
考虑有 0 边的时候有什么影响
就是如果存在一个 0 环或一个 0 - 强连通分量在 1 - n 的一条长度
≤
d
i
s
+
k
\le dis+k
≤dis+k 的路径上
为了支持这个判断
我们可以先把 0 边拿出来跑强连通分量,枚举一条两个端点在一个分量里的 0 边,对两个端点分别求出到 1 和到 n 的最短路
于是建返图,跑从 1 和 n 开始的最短路即可
对细节的要求比较高
#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
cs int N = 1e5 + 5, M = 2e5 + 5, K = 52;
int T, n, m, k, p, dp[N][K];
void Add(int &a, int b){ a = a + b >= p ? a + b - p : a + b; }
struct Dijsktra{
int first[N], nxt[M], to[M], w[M], tot;
void add(int x, int y, int z){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y, w[tot] = z;}
int dis[N], st; bool vis[N];
typedef pair<int, int> pi;
#define mp make_pair
void clear(){ memset(first, 0, sizeof(first)); tot = 0; }
void Dij(){
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
priority_queue<pi> q; q.push(mp(0, st));
dis[st] = 0;
while(!q.empty()){
int x = q.top().second; q.pop();
if(vis[x]) continue; vis[x] = true;
for(int i = first[x]; i; i = nxt[i]){
int t = to[i]; if(dis[t] > dis[x] + w[i]){
dis[t] = dis[x] + w[i]; q.push(mp(-dis[t], t));
}
}
}
}
}G[2];
int dfs(int u, int delta){
if(~dp[u][delta]) return dp[u][delta];
int ans = 0;
for(int i = G[1].first[u]; i; i = G[1].nxt[i]){
int t = G[1].to[i], now = G[1].w[i] - G[0].dis[u] + G[0].dis[t];
if(now <= delta) Add(ans, dfs(t, delta - now));
} return dp[u][delta] = ans;
}
int DP(){
memset(dp, -1, sizeof(dp));
int ans = 0; dp[1][0] = 1;
for(int i = 0; i <= k; i++) Add(ans, dfs(n, i));
return ans;
}
namespace Tarjan{
int first[N], nxt[M], to[M], tot;
void add(int x, int y){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y; }
int dfn[N], low[N], sta[N]; bool insta[N]; int idx[N], ct, sign, top;
void clear(){
memset(first, 0, sizeof(first)); tot = 0;
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low)); top = sign = ct = 0;
}
void dfs(int u){
dfn[u] = low[u] = ++sign;
sta[++top] = u; insta[u] = true;
for(int i = first[u]; i; i = nxt[i]){
int t = to[i]; if(!dfn[t]) dfs(t), low[u] = min(low[u], low[t]);
else if(insta[t] && dfn[t] < low[u]) low[u] = dfn[t];
}
if(dfn[u] == low[u]){ ++ct; do{ idx[sta[top]] = ct; insta[sta[top]] = 0; }while(sta[top--]^u); }
}
void Solve(){ for(int i = 1; i <= n; i++) if(!dfn[i]) dfs(i); }
bool ck(){
if(idx[1] == idx[n]) return true;
for(int i = 1; i <= n; i++){
for(int e = first[i]; e; e = nxt[e]){
if(idx[i] ^ idx[to[e]]) continue;
if(G[0].dis[i] + G[1].dis[to[e]] <= G[0].dis[n] + k) return true;
}
} return false;
}
}
void Solve(){
n = read(), m = read(), k = read(), p = read();
G[0].st = 1;
G[1].st = n;
for(int i = 1; i <= m; i++){
int x = read(), y = read(), z = read();
G[0].add(x, y, z);
G[1].add(y, x, z);
if(z == 0) Tarjan::add(x, y);
}
G[0].Dij();
if(G[0].dis[n] > 1e9){ puts("-1"); return; }
G[1].Dij();
Tarjan::Solve();
if(Tarjan::ck()){ puts("-1"); return; }
cout << DP() << '\n';
}
void Clear(){
G[0].clear();
G[1].clear();
Tarjan::clear();
}
int main(){
T = read();
while(T--) Solve(), Clear();
return 0;
}