题面
解法
开始认认真真学习NOIP原题
- 讲两种不同的做法吧:
- 第一种做法:求出从 1 1 1号点到其它所有点的距离和 n n n号点到其它所有点的距离(这个需要建出反向边之后来跑),然后我们设 f [ i ] [ j ] f[i][j] f[i][j]表示从 1 1 1走到点 i i i且当前走过的路径长度比 1 1 1到 i i i的最短路大 j j j的方案数
- 考虑这种做法如何转移,我们现在需要确定 f f f数组的更新顺序。当存在长度为 0 0 0的边的时候,距离1相同的顺序可能有先后变动。那么我们可以把所有长度为 0 0 0的边建成一个新图,对于这个新图我们做一遍拓扑排序,如果出现 0 0 0环且上面的点满足 d i s 1 [ i ] + d i s n [ i ] < = d i s 1 [ n ] + K dis1[i]+disn[i]<=dis1[n]+K dis1[i]+disn[i]<=dis1[n]+K,那么这时一定为 − 1 -1 −1,否则按照 d i s 1 [ i ] + d i s n [ i ] dis1[i]+disn[i] dis1[i]+disn[i]作为第一关键字,拓扑序作为第二关键字转移就可以了
- 具体的状态转移方程比较简单,假设当前的状态为 f [ x ] [ k ] f[x][k] f[x][k],现在有一条有向边 ( x , y , v ) (x,y,v) (x,y,v),那么 f [ x ] [ k ] f[x][k] f[x][k]这个状态就可以转移到状态 f [ y ] [ d i s 1 [ x ] + k + v − d i s 1 [ y ] ] f[y][dis1[x]+k+v-dis1[y]] f[y][dis1[x]+k+v−dis1[y]]
- 时间复杂度: O ( T m k ) O(Tmk) O(Tmk),实现起来比较繁琐
- 第二种做法:这一种做法相对第一种来说比较简单。我们考虑反向地dp,即最后我们要求的是 ∑ i = 0 K f [ 1 ] [ i ] \sum_{i=0}^K f[1][i] ∑i=0Kf[1][i]。转移和第一种方法的转移是一样的,这个我们可以使用记忆化搜索实现。那么如何判断 0 0 0环??如果一个状态已经在栈中,并且又被访问到,那么一定存在 0 0 0环。
- 时间复杂度: O ( T m k ) O(Tmk) O(Tmk),实现起来较为简单
- 以上两种做法均可通过此题,建议使用第二种做法
- 如果要使最后的程序效率比较优秀,那么请尽量使用dijkstra算法。因为我比较懒,所以就只使用了spfa
代码
#include <bits/stdc++.h>
#define PI pair <int, int>
#define mp make_pair
#define inf 1 << 30
#define N 100001
using namespace std;
template <typename node> void chkmax(node &x, node y) {x = max(x, y);}
template <typename node> void chkmin(node &x, node y) {x = min(x, y);}
template <typename node> void read(node &x) {
x = 0; int f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
int n, m, K, p, flag, dis[N], used[N], f[N][51], vis[N][51];
vector <PI> e[N], E[N];
void spfa() {
for (int i = 1; i <= n; i++) dis[i] = inf, used[i] = 0;
dis[n] = 0; queue <int> q; q.push(n);
while (!q.empty()) {
int x = q.front(); q.pop(); used[x] = 0;
for (int i = 0; i < E[x].size(); i++) {
PI tmp = E[x][i]; int k = tmp.first, v = tmp.second;
if (dis[k] > dis[x] + v) {
dis[k] = dis[x] + v;
if (!used[k]) q.push(k), used[k] = 1;
}
}
}
}
int dfs(int x, int y) {
if (vis[x][y]) return flag = false, 0;
if (f[x][y]) return f[x][y]; vis[x][y] = 1;
int sum = 0;
for (int i = 0; i < e[x].size(); i++) {
PI tx = e[x][i];
int k = tx.first, v = tx.second;
int tmp = y + dis[x] - v - dis[k];
if (tmp < 0 || tmp > K) continue;
(sum += dfs(k, tmp)) %= p;
if (!flag) return 0;
}
if (x == n && y == 0) sum = 1;
return vis[x][y] = 0, f[x][y] = sum;
}
int main() {
int T; read(T);
while (T--) {
read(n), read(m), read(K), read(p);
for (int i = 1; i <= n; i++) e[i].clear(), E[i].clear();
for (int i = 1; i <= m; i++) {
int x, y, v; read(x), read(y), read(v);
e[x].push_back(mp(y, v));
E[y].push_back(mp(x, v));
}
spfa();
memset(f, 0, sizeof(f)); int ans = 0;
memset(vis, 0, sizeof(vis)); flag = true;
for (int i = 0; i <= K; i++) (ans += dfs(1, i)) %= p;
if (!flag) puts("-1"); else printf("%d\n", ans);
}
return 0;
}