题意:给一张帯权有向图,求 1 1 1 到 n n n 长度不超过最短路长度 + k +k +k 的路径条数 模 P P P。有无数条输出 − 1 -1 −1 。
n ≤ 1 0 5 , m ≤ 2 × 1 0 5 , k ≤ 50 n\leq 10^5,m\leq 2\times 10^5,k\leq 50 n≤105,m≤2×105,k≤50,边权非负
先往最短路图想
发现 k k k 很小,考虑硬上 dp。
设 f ( u , k ) f(u,k) f(u,k) 表示从 u u u 到 n n n 多走 k k k 的路径数。
转移枚举下一步往哪里走。
f ( u , k ) = ∑ ( u , v ) ∈ E f ( v , k − ( w ( u , v ) + d i s u − d i s v ) ) f(u,k)=\sum_{(u,v)\in E}f(v,k-(w(u,v)+dis_u-dis_v)) f(u,k)=(u,v)∈E∑f(v,k−(w(u,v)+disu−disv))
其中 d i s u dis_u disu 为 1 1 1 到 u u u 最短路长度。
在没有零边的基础上,右边这块 w ( u , v ) + d i s u − d i s v w(u,v)+dis_u-dis_v w(u,v)+disu−disv 取 0 0 0 当且仅当 ( u , v ) (u,v) (u,v) 在最短路图上。因为这是个 DAG,所以在最短路图上跑拓扑排序,然后枚举 k k k 更新就可以了。这样可以拿到 70 70 70 分的好成绩。
有零边时可能会出现 d i s n + k dis_n+k disn+k 内可达的零环,这样答案为无穷大。如果拓扑排序后点 i i i 入度不为 0 0 0 说明在零环上,此时如果 d i s i + d i s i ′ ≤ d i s n + k dis_i+dis'_i \leq dis_n+k disi+disi′≤disn+k 就输出 − 1 -1 −1。( d i s ′ dis' dis′ 为到 n n n 的最短距离)
否则的话反正都走不到这里,直接把伪的拓扑序继续往后做。
复杂度 O ( n log n + n k ) O(n\log n+nk) O(nlogn+nk)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <utility>
#include <queue>
#define MAXN 100005
#define MAXM 200005
using namespace std;
int n,m,k,P;
inline int add(const int& x,const int& y){return x+y>=P? x+y-P:x+y;}
inline int read()
{
int ans=0;
char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
struct edge{int u,v,w;}e[MAXM];
int head[MAXN],nxt[MAXM],cnt;
inline void addnode(int u,int v,int w)
{
e[++cnt]=(edge){u,v,w};
nxt[cnt]=head[u];
head[u]=cnt;
}
template<typename T,typename cmp=less<T> >
struct heap
{
T val[MAXM];
T* end;
heap(){end=val;}
inline bool empty(){return end==val;}
inline void push(const T& v){*(end++)=v;push_heap(val,end,cmp());}
inline T pop(){pop_heap(val,end,cmp());return *(--end);}
};
typedef pair<int,int> pi;
heap<pi,greater<pi> > q;
int dis[MAXN],disn[MAXN],deg[MAXN];
inline void dij(int* dis,int s=1)
{
memset(dis,0x3f,sizeof(disn));
dis[s]=0;
q.push(make_pair(0,s));
while (!q.empty())
{
int u=q.pop().second;
for (int i=head[u];i;i=nxt[i])
if (dis[u]+e[i].w<dis[e[i].v])
dis[e[i].v]=dis[u]+e[i].w,q.push(make_pair(dis[e[i].v],e[i].v));
}
}
int lis[MAXN],vis[MAXN],tim;
void dfs(int u)
{
vis[u]=1;
for (int i=head[u];i;i=nxt[i])
if (dis[u]+e[i].w==dis[e[i].v])
{
++deg[e[i].v];
if (!vis[e[i].v]) dfs(e[i].v);
}
}
inline void topsort()
{
queue<int> q;
tim=0;
q.push(1);
while (!q.empty())
{
int u=q.front();q.pop();
lis[++tim]=u;
for (int i=head[u];i;i=nxt[i])
if (dis[u]+e[i].w==dis[e[i].v]&&(--deg[e[i].v]==0))
q.push(e[i].v);
}
}
int f[55][MAXN],u[MAXM],v[MAXM],w[MAXM];
int main()
{
for (int T=read();T;T--)
{
cnt=tim=0;
memset(head,0,sizeof(head));
memset(nxt,0,sizeof(nxt));
memset(deg,0,sizeof(deg));
memset(vis,0,sizeof(vis));
n=read(),m=read(),k=read(),P=read();
for (int i=1;i<=m;i++) u[i]=read(),v[i]=read(),w[i]=read(),addnode(v[i],u[i],w[i]);
dij(disn,n);
cnt=0;
memset(head,0,sizeof(head));
memset(nxt,0,sizeof(nxt));
for (int i=1;i<=m;i++) addnode(u[i],v[i],w[i]);
dij(dis);
dfs(1);
topsort();
int ans=0;
for (int i=1;i<=n;i++) if (deg[i]&&dis[i]+disn[i]<=dis[n]+k) {puts("-1");goto end;}
memset(f,0,sizeof(f));
f[0][n]=1;
for (int t=0;t<=k;t++)
for (int x=tim;x>=1;x--)
{
int u=lis[x];
for (int i=head[u];i;i=nxt[i])
{
int del=e[i].w-dis[e[i].v]+dis[u];
if (t>=del) f[t][u]=add(f[t][u],f[t-del][e[i].v]);
}
}
for (int i=0;i<=k;i++) ans=add(ans,f[i][1]);
printf("%d\n",ans);
end:;
}
return 0;
}