Description
策策同学特别喜欢逛公园。公园可以看成一张n个点m条边构成的有向图,且没有自环和重边。其中1号点是公园的入口,n号点是公园的出口,每条边有一个非负权值,代表策策经过这条边所要花的时间。
策策每天都会去逛公园,他总是从1号点进去,从n号点出来。
策策喜欢新鲜的事物,他不希望有两天逛公园的路线完全一样,同时策策还是一个特别热爱学习的好孩子,他不希望每天在逛公园这件事上花费太多的时间。如果1号点到n号点的最短路长为e,那么策策只会喜欢长度不超过e + k的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮他吗?
为避免输出过大,答案对p取模。
如果有无穷多条合法的路线,请输出−1。
Input
输入文件名为 park.in 。
第一行包含一个整数 T, 代表数据组数。
接下来T组数据,对于每组数据:
第一行包含四个整数 n,m,k,p,每两个整数之间用一个空格隔开。
接下来N行,每行三个整数
bi,ci,di
, 代表编号为
bi,ci
的点之间有一条权值为
di
的有向边,每两个整数之间用一个空格隔开。
Output
输出文件包含 T 行,每行一个整数代表答案。
Sample Input
2
5 7 2 10
1 2 1
2 4 0
4 5 2
2 3 2
3 4 1
3 5 2
1 5 3
2 2 0 10
1 2 0
2 1 0
Sample Output
3
-1
样例说明:
对于第一组数据,最短路为 3。
1 – 5, 1 – 2 – 4 – 5, 1 – 2 – 3 – 5 为 3 条合法路径。
Data Constraint
对于不同的测试点,我们约定各种参数的规模 不会超过如下
对于 100%的数据, 1≤p≤109,1≤bi,ci≤n,0≤di≤1000 。
Solution
这题如果直接DP的话,会发现有后效性,则会重复统计答案。
但看到 k≤50 ,很小,于是我们考虑拆点。
先做一次 Spfa ,设 1 到 i 号点的最短路为 dis[i] 。
之后把每个点拆成 k+1 个点,分别对应到这个点时的路径长 j−dis[i] 的值。
由于这个值的范围只在 [0,k] 之间,
那么我们对于开始时的有向边的两个点拆点,并进行进行连接。
这样我们就构成了一个拓扑图,跑一遍拓扑排序即可。
当跑完后发现并没有遍历所有点,则直接输出 -1 即可。
而且这题还要卡卡常,发现连边时连接了很多无用点,拖慢了拓扑排序的速度。
于是我们考虑倒着做一遍 Spfa (从 n 开始)。
当一个点 dis[i]+dis1[i]>dis[n]+k 时,说明这个点就没用了,不需要从它连边出去。
时间复杂度 O(T∗M∗K) 。
Code
#include<cstdio>
#include<cstring>
#include<cctype>
using namespace std;
const int N=1e5+1,K=51;
int n,m,k,p,tot,ans=0;
int first[N],next[N<<1],en[N<<1],w[N<<1];
int first1[N*K],next1[N*K<<1],en1[N*K<<1],d[N*K];
int q[N<<6],dis[N],dis1[N],u[N<<1],v[N<<1],t[N<<1],f[N*K];
bool bz[N];
inline int read()
{
int X=0,w=1; char ch=0;
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
return X*w;
}
inline void insert(int x,int y,int z)
{
next[++tot]=first[x];
first[x]=tot;
en[tot]=y;
w[tot]=z;
}
inline void insert1(int x,int y)
{
next1[++tot]=first1[x];
first1[x]=tot;
en1[tot]=y;
d[y]++;
}
inline int get(int x,int y)
{
return (x-1)*(k+1)+y+1;
}
int main()
{
int T=read();
while(T--)
{
n=read(),m=read(),k=read(),p=read();
memset(first,tot=0,sizeof(first));
for(int i=1;i<=m;i++)
{
u[i]=read(),v[i]=read(),t[i]=read();
insert(u[i],v[i],t[i]);
}
memset(dis,60,sizeof(dis));
int l=dis[1]=0,r=q[1]=1;
while(l<r)
{
int x=q[++l];
bz[x]=false;
for(int i=first[x];i;i=next[i])
if(dis[x]+w[i]<dis[en[i]])
{
dis[en[i]]=dis[x]+w[i];
if(!bz[en[i]]) bz[q[++r]=en[i]]=true;
}
}
memset(first,tot=0,sizeof(first));
for(int i=1;i<=m;i++) insert(v[i],u[i],t[i]);
memset(dis1,60,sizeof(dis1));
l=dis1[q[r=1]=n]=0;
while(l<r)
{
int x=q[++l];
bz[x]=false;
for(int i=first[x];i;i=next[i])
if(dis1[x]+w[i]<dis1[en[i]])
{
dis1[en[i]]=dis1[x]+w[i];
if(!bz[en[i]]) bz[q[++r]=en[i]]=true;
}
}
memset(first1,tot=0,sizeof(first1));
memset(d,0,sizeof(d));
for(int i=1;i<=m;i++)
{
int x=get(u[i],0),y=get(v[i],dis[u[i]]+t[i]-dis[v[i]]);
for(int j=dis[u[i]];j+t[i]+dis1[v[i]]<=dis[n]+k;j++,x++,y++) insert1(x,y);
}
int num=(k+1)*n,sum=0;
l=r=ans=0;
memset(f,0,sizeof(f));
for(int i=1;i<=num;i++)
if(!d[i]) q[++r]=i;
f[1]=1;
while(l<r)
{
int x=q[++l];
sum++;
for(int i=first1[x];i;i=next1[i])
{
if(!--d[en1[i]]) q[++r]=en1[i];
f[en1[i]]+=f[x];
f[en1[i]]=f[en1[i]]>p?f[en1[i]]-p:f[en1[i]];
}
}
if(sum<num) printf("-1\n"); else
{
for(int i=0;i<=k;i++) ans=(ans+f[get(n,i)])%p;
printf("%d\n",ans);
}
}
return 0;
}