题意
题解
首先处理合法路线无穷多的情况。合法路线无穷多当且仅当图中出现权值和为 0 0 0 的环,且存在一条经过环上的点且权值和不超过 D + K D+K D+K 的路径。
求解这样的环,可以对边权为 0 0 0 的边建正图以及反图,分别进行拓扑排序。那么权值和为 0 0 0 的环上的点 x x x 满足下述两个条件:存在与 x x x 相连的边权为 0 0 0 的边; x x x 在正、反图拓扑排序中入度始终大于 0 0 0。原理是正、反图拓扑排序分别能够处理与环相连的入边与出边。
若存在这样的环,需要枚举环上的点判断是否存在一条经过该点且权值和不超过 D + K D+K D+K 的路径。同样可以通过建原图的正图以及反图进行求解。应用 D i j k s t r a Dijkstra Dijkstra 在正、反图上分别求解以 0 , N − 1 0,N-1 0,N−1 为源点的单源最短路,分别记为 d s , r d s ds,rds ds,rds。那么经过 x x x 的路径最短长度为 d s [ x ] + r d s [ x ] ds[x]+rds[x] ds[x]+rds[x]。
若合法路线数量有限,对答案有贡献的点 x x x 都满足 d s [ x ] + r d s [ x ] ≤ D + K ds[x]+rds[x]\leq D+K ds[x]+rds[x]≤D+K,此时则不会处理边权为 0 0 0 的环上的点。那么搜索路径时,若从点 x x x 出发又返回 x x x,必定经过正权环。经过 x x x 且满足权值和为 [ D , D + K ] [D,D+K] [D,D+K] 的路径,在 x x x 到汇点 N − 1 N-1 N−1 的这一段路径,权值和只可能为 [ r d s [ x ] , r d s [ x ] + K ] \big[rds[x],rds[x]+K\big] [rds[x],rds[x]+K]。那么可以用 K + 1 K+1 K+1 个状态对这样的路径进行表示。 d p [ x ] [ d ] dp[x][d] dp[x][d] 代表从节点 x x x 出发到 N − 1 N-1 N−1 且长度为 r d s [ x ] + d rds[x]+d rds[x]+d 的路径数量,这样的状态搜索满足 D A G DAG DAG 的性质,可以记忆化求解
d p [ x ] [ d ] = ∑ d p [ y ] [ d + r d s [ x ] − c o s t ( x , y ) − r d s [ y ] ] , e ( x , y ) ∈ G dp[x][d]=\sum dp[y][d+rds[x]-cost(x,y)-rds[y]], e(x,y)\in G dp[x][d]=∑dp[y][d+rds[x]−cost(x,y)−rds[y]],e(x,y)∈G 只有满足 d s [ x ] + r d s [ x ] + d ≤ D + K ds[x]+rds[x]+d\leq D+K ds[x]+rds[x]+d≤D+K 的状态可能对答案有贡献,那么搜索时可以对不满足条件的状态进行剪枝。总时间复杂度 O ( N log M + N K ) O(N\log M+NK) O(NlogM+NK)。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int maxk = 55, maxn = 100005;
struct edge
{
int to, cost;
};
int T, N, M, K, P, maxlen, d0[maxn], rd0[maxn];
int ds[maxn], rds[maxn], mem[maxn][maxk];
vector<edge> G[maxn], rG[maxn], G0[maxn], rG0[maxn];
bool used[maxn], loop[maxn];
void topsort(vector<edge> G[], int in[])
{
queue<int> q;
for (int i = 0; i < N; ++i)
if (!in[i])
q.push(i);
while (q.size())
{
int x = q.front();
q.pop();
for (auto &e : G[x])
if (!--in[e.to])
q.push(e.to);
}
for (int i = 0; i < N; ++i)
loop[i] &= in[i];
}
void dijkstra(vector<edge> G[], int s, int ds[])
{
priority_queue<pii, vector<pii>, greater<pii>> q;
memset(ds, 0x3f, sizeof(int) * N);
memset(used, 0, sizeof(bool) * N);
ds[s] = 0, q.push({
0, s});
while (q.size())
{
int x = q.top().second;
q.pop();
if (used[x])
continue;
used[x] = 1;
for (auto &e : G[x])
{
int y = e.to, d = ds[x] + e.cost;
if (d < ds[y])
ds[y] = d, q.push({
d, y});
}
}
}
int rec(int x, int d)
{
if (mem[x][d] != -1)
return mem[x][d];
int res = 0;
for (auto &e : G[x])
{
int y = e.to, d2 = d + rds[x] - e.cost - rds[y];
if (0 <= d2 && d2 <= K && d2 + rds[y] + ds[y] <= maxlen)
res = (res + rec(y, d2)) % P;
}
return mem[x][d] = res;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> T;
while (T--)
{
cin >> N >> M >> K >> P;
memset(d0, 0, sizeof(int) * N);
memset(rd0, 0, sizeof(int) * N);
memset(loop, 0, sizeof(bool) * N);
for (int i = 0; i < N; ++i)
G[i].clear(), rG[i].clear(), G0[i].clear(), rG0[i].clear();
for (int i = 0; i < M; ++i)
{
int a, b, c;
cin >> a >> b >> c;
--a, --b;
G[a].push_back(edge{
b, c}), rG[b].push_back(edge{
a, c});
if (c == 0)
{
loop[a] = loop[b] = 1;
++d0[b], --rd0[a];
G0[a].push_back(edge{
b, c}), rG0[b].push_back(edge{
a, c});
}
}
topsort(G0, d0), topsort(rG0, rd0);
dijkstra(G, 0, ds), dijkstra(rG, N - 1, rds);
maxlen = ds[N - 1] + K;
bool is_inf = 0;
for (int i = 0; i < N; ++i)
if (loop[i] && ds[i] + rds[i] <= maxlen)
{
is_inf = 1;
break;
}
if (is_inf)
{
cout << -1 << '\n';
continue;
}
memset(mem, -1, sizeof(mem));
mem[N - 1][0] = 1;
int res = 0;
for (int i = 0; i <= K; ++i)
res = (res + rec(0, i)) % P;
cout << res << '\n';
}
return 0;
}