题目链接:洛谷-3953
主要思路:
由于这道题K的数值范围较小,故可以直接枚举K来求答案。答案就是起点到终点的为d,d+1,d+2,...,d+K的路径的条数总合。
联想到之前一些在路径上有消耗的图论题,我们可以把这个K也当做是一个被消耗的东西,就像钱一样(以下就当钱讲好了)。故我们可以定义dp[i][j]为从i这个点出发走比他到终点的最短距离大j的路径有多少条。接下来就是状态转移的问题了 。
记你的当前所在的点X到终点的最短距离为Dis1,现在你要走到点Y,他到终点的最短距离为Dis2,X到Y的边长为D。那么你从X走Y到终点的最短距离为Dis2+D,而你走点X到终点的最短路径(不用管有没有经过Y)为Dis1,故消耗的钱就是Dis2+D-Dis1,即为cost。故dp[X][j]=∑dp[Y][j-cost](若钱不够是不能走的,但是钱刚好花完是可以走的,也就是不要欠钱就可以了一直走下去)终止状态就是终点且剩余钱数为0,这样只有一种方案。(到达终点时若策策还有钱,就让策策去把钱正好花完,花不完是不能计数的)。
那么就能发现如果能走到一个0环上且手上的钱不为负数,那么他可以沿着0环随便走多少步再到终点(0环上的点到终点的最短距离都是一样的),那么这样就得输出-1,但是前提条件是他能用手上的钱走到0环。
判0环就让记录在这条路上你上次到达这个点还剩多少钱,若与这个点一样就说明这个点在0环上。(可能有些人会问,从0环走出来没办法正好把钱花完走向终点怎么办,出门少带钱就可以了(如果你没有和我一样的疑问可以忽略))
每个点到终点的最短距离就建一个方向图跑最短路就可以了。
AC代码:
#include<cstdio>
#include<cstring>
#include<queue>
#define M 200005
using namespace std;
int dis[M];
struct Graph {
struct E {
int to,nx,d;
} edge[M];
int tot,head[M];
void Init() {memset(head,0,sizeof(head)),tot=0;}
void Addedge(int a,int b,int d) {
edge[++tot].to=b;
edge[tot].nx=head[a];
edge[tot].d=d;
head[a]=tot;
}
} G1,G2;
struct Dijstra {
struct node {
int id,d;
bool operator <(const node &x)const {
return d>x.d;
}
node(int id,int d):id(id),d(d) {}
};
priority_queue<node>Q;
int cnt[M];
bool mark[M];
void solve(int st) {
Q.push(node(st,0));
memset(dis,63,sizeof(dis));
memset(mark,0,sizeof(mark));
dis[st]=0;
while(!Q.empty()) {
int now=Q.top().id;
Q.pop();
if(mark[now])continue;
mark[now]=1;
for(int i=G2.head[now]; i; i=G2.edge[i].nx) {//在反向图上跑每个点到终点的最短路
int nxt=G2.edge[i].to;
if(dis[now]+G2.edge[i].d<dis[nxt]) {
dis[nxt]=dis[now]+G2.edge[i].d;
Q.push(node(nxt,dis[nxt]));
}
}
}
}
} S;
int P,K,n;
bool have_ring;
bool in_ring[M][55];
int dp[M][55];//dp[i][j]表示从i出发,比最短路多走的路程为j的方案总数
int dfs(int now,int now_k) {
if(in_ring[now][now_k]) {
have_ring=1;//判是否存在0环
return 0;
}
if(dp[now][now_k]>=0)return dp[now][now_k];//记忆化
in_ring[now][now_k]=1;
int& res=dp[now][now_k];
res=0;
for(int i=G1.head[now]; i; i=G1.edge[i].nx) {
int nxt=G1.edge[i].to,nxtd=now_k-(G1.edge[i].d-(dis[now]-dis[nxt]));//nxtd为剩下的钱
if(nxtd<0||nxtd>K)continue;//超出边界,不能走
res=(res+dfs(nxt,nxtd))%P;
dfs(nxt,nxtd);
if(have_ring)return 0;//如果有环就直接退出
}
in_ring[now][now_k]=0;
if(now==n&&now_k==0)res=1;//到达终点放在这里判,防止终点在0环上。注意只有钱花完且到达终点才停止。
return res;
}
void Init() {
have_ring=0;
memset(in_ring,0,sizeof(in_ring));
memset(dp,-1,sizeof(dp));
G1.Init(),G2.Init();
}
int main() {
int T;
scanf("%d",&T);
while(T--) {
Init();
int m;
scanf("%d%d%d%d",&n,&m,&K,&P);
for(int i=1; i<=m; i++) {
int a,b,d;
scanf("%d%d%d",&a,&b,&d);
G1.Addedge(a,b,d);//建正向边
G2.Addedge(b,a,d);//建反向边
}
S.solve(n);//预处理终点到每个点的最短距离
int ans=0;
for(int i=0; i<=K; i++) {//循环从0开始
ans=(ans+dfs(1,i))%P;
if(have_ring)break;
}
if(have_ring)puts("-1");
else printf("%d\n",ans);
}
return 0;
}