设dp[i][j]为从1到i长度为最短路+j的路径数量,dis1为从1到每个点的最短距离,disn为从n到每个点的最短距离。先不考虑0边,dp[u][j]可以转移到v的dis[u]+w(u,v)-dis[v]+j,即dp[u][j]->dp[v][dis[u]+w(u,v)-dis[v]+j]。dis[u]+w(u,v)-dis[v]即松弛距离。在有向图上,可能前面的点会由后面的点松弛到,因此dp转移的时候外层枚举0->k,内层枚举按dis1排序的节点,分层转移。
接下来考虑0边的影响。0边可能会产生0环,如果0环恰好在有效路径内,则会出现无穷条路径满足情况。如果0环上的点满足dis1[i]+disn[i]<=dis1[n]+k,那么这个0环跑无穷多次都是满足题意的,判断这种情况输出-1。于是要找出在0环上的点,把所有0边取出来建图进行拓扑排序,如果排序完有入度不为0的点则存在0环。但跑一次拓扑只是把所有指向0环的边摘掉了,可能还有0环指向的边存在于图中。因此建0边的反图,再跑一次拓扑排序,把所有原图中0环指向的边也摘掉,剩下的既被0边连接又没有被两次拓扑摘掉的点就是0环上的点。
存在0边以后,如果dp时仍然只按dis1排序,在0边相连的点之间可能会存在顺序问题。例如,存在x->y->z,中间相连的两条边都为0,那么转移的时候应该由x到y再到z,实际上因为三个点的dis1相同,可能不会按正确顺序排序。那么在第一次拓扑排序的时候顺便记录与0边相连的点的拓扑序,dp时排序以dis1为第一关键字,拓扑序为第二关键字即可。

#include<iostream> #include<cstdio> #include<queue> #include<algorithm> #include<cstring> using namespace std; const int inf=214748364; int a[100010],now; int t,n,m,k,p,ru[100010],tag[100010],flag,zero[100010],zer,vis1[100010],vis2[100010]; long long ans; int dis[100010],vis[100010]; bool cmp(int x,int y){ if(dis[x]==dis[y])return tag[x]<tag[y]; else return dis[x]<dis[y]; } struct edg{ int x,y; edg(int a=0,int b=0){ x=a,y=b; } }ze[200010]; int ver[200010],Next[200010],head[100010],tot,edge[200010]; void add(int x,int y,int z){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; edge[tot]=z; } int ver1[200010],Next1[200010],head1[100010],tot1,edge1[200010]; void add1(int x,int y,int z){ ver1[++tot1]=y; Next1[tot1]=head1[x]; head1[x]=tot1; edge1[tot1]=z; } priority_queue<pair<int,int> >q; void dij(){ while(q.size())q.pop(); for(int i=1;i<=n;i++)dis[i]=inf,vis[i]=0; dis[1]=0; q.push(make_pair(0,1)); while(!q.empty()){ while(q.size()&&vis[q.top().second])q.pop(); if(q.empty())break; int x=q.top().second; q.pop(); vis[x]=1; for(int i=head[x];i;i=Next[i]){ int y=ver[i]; if(dis[y]>dis[x]+edge[i]){ dis[y]=dis[x]+edge[i]; q.push(make_pair(-dis[y],y)); } } } } int dis1[100010]; void dij1(){ while(q.size())q.pop(); for(int i=1;i<=n;i++)dis1[i]=inf,vis[i]=0; dis1[n]=0; q.push(make_pair(0,n)); while(!q.empty()){ while(q.size()&&vis[q.top().second])q.pop(); if(q.empty())break; int x=q.top().second; q.pop(); vis[x]=1; for(int i=head1[x];i;i=Next1[i]){ int y=ver1[i]; if(dis1[y]>dis1[x]+edge1[i]){ dis1[y]=dis1[x]+edge1[i]; q.push(make_pair(-dis1[y],y)); } } } } queue<int>qq; void tp1(){ while(qq.size())qq.pop(); for(int i=1;i<=n;i++){ if(!ru[i]&&zero[i])qq.push(i); } while(qq.size()){ int x=qq.front(); qq.pop(); tag[x]=++now; vis1[x]=1; for(int i=head1[x];i;i=Next1[i]){ int y=ver1[i]; ru[y]--; if(!ru[y]){ qq.push(y); } } } } void tp2(){ while(qq.size())qq.pop(); for(int i=1;i<=n;i++){ if(!ru[i]&&zero[i])qq.push(i); } while(qq.size()){ int x=qq.front(); qq.pop(); vis2[x]=1; for(int i=head1[x];i;i=Next1[i]){ int y=ver1[i]; ru[y]--; if(!ru[y]){ qq.push(y); } } } for(int i=1;i<=n;i++){ if(zero[i]&&!vis1[i]&&!vis2[i]){ if(dis[i]+dis1[i]<=dis[n]+k)flag=1; } } } long long dp[100010][51]; void work(){ dp[1][0]=1%p; for(int i=0;i<=k;i++){ for(int j=1;j<=n;j++){ int x=a[j]; if(!dp[x][i])continue; for(int l=head[x];l;l=Next[l]){ int y=ver[l]; if(i+dis[x]+edge[l]-dis[y]<=k){ dp[y][i+dis[x]+edge[l]-dis[y]]=(dp[y][i+dis[x]+edge[l]-dis[y]]+dp[x][i])%p; } } } } } void clear(){ now=zer=ans=tot=tot1=flag=0; memset(dp,0,sizeof(dp)); memset(ru,0,sizeof(ru)); memset(head,0,sizeof(head)); memset(head1,0,sizeof(head1)); memset(tag,0,sizeof(tag)); memset(vis1,0,sizeof(vis1)); memset(vis2,0,sizeof(vis2)); memset(zero,0,sizeof(zero)); } int main(){ // freopen("1.in","r",stdin); scanf("%d",&t); while(t--){ scanf("%d%d%d%d",&n,&m,&k,&p); clear(); for(int i=1,x,y,z;i<=m;i++){ scanf("%d%d%d",&x,&y,&z); if(!z){ ze[++zer]=edg(x,y); zero[x]=zero[y]=1; } add(x,y,z); add1(y,x,z); } dij(); dij1(); memset(head1,0,sizeof(head1)); tot1=0; for(int i=1;i<=zer;i++){ add1(ze[i].x,ze[i].y,0); ru[ze[i].y]++; } tp1(); memset(head1,0,sizeof(head1)); memset(ru,0,sizeof(ru)); tot1=0; for(int i=1;i<=zer;i++){ add1(ze[i].y,ze[i].x,0); ru[ze[i].x]++; } tp2(); if(flag){ printf("-1 "); continue; } for(int i=1;i<=n;i++){ a[i]=i; } sort(a+1,a+n+1,cmp); work(); for(int i=0;i<=k;i++){ ans=(ans+dp[n][i])%p; } printf("%lld ",ans); } return 0; }