题目:https://www.luogu.org/problem/P3953
分析:
建原图求最短路,建反图dp。
spfa:
求出点1到点 i 的最小距离dis[i]。
记忆化搜索:
dp。记忆化搜索相对好写。
定义dp[i][t]表示从1号点走到i号点,路径长度为 dis[i]+t 的方案总数。
如果dp[i][t]的值已经求过,则直接返回。
本人第一个代码用了以下记忆化搜索的代码:
if(dp[u][t])return dp[u][t];
导致TLE。
原因在于没有注意到0边,当dp[i][t]=0时有可能dp[i][t]已经求过,按上面的代码,是将继续重复求值,从而TLE。
作以下修改,就AC了本题。
if(dp[u][t]!=-1)return dp[u][t];
状态转移方程:
dp[u][t]=sigema(dp[v][ dis[u]-dis[v]-val(v,u)+t ],反图边(v,u)中,v为u的后继。
剪枝一:
t<0或t>K,return 0
剪枝二:
dis[v]>dis[n]+K,continue
AC代码:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int MaxN=1e5+1,MaxM=2e5+1;
int T,n,m,K,p;
int dis[MaxN],dp[MaxN][52];
//dis[u]表示从1号节点走到u号节点的最短路长度,
//f[u][k]表示从1号节点走到u号节点,路径长度为dis[u]+k的方案总数。
struct Edge{
int from,to,val,next;
}edge1[MaxM],edge2[MaxM];
inline int read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
int tot,cns,head1[MaxN],head2[MaxN];
void add1(int from,int to,int val){
edge1[++tot].next=head1[from];
edge1[tot].to=to;
edge1[tot].val=val;
head1[from]=tot;
}
void add2(int from,int to,int val){
edge2[++cns].next=head2[from];
edge2[cns].to=to;
edge2[cns].val=val;
head2[from]=cns;
}
int head,tail,que[MaxN*2+7],u,v,val;
int exist[MaxN];
void spfa(){
dis[1]=0;
int head=0,tail=1;
que[1]=1;exist[1]=1;
while(head!=tail){
++head;
head=(head-1)%(MaxN*2+5)+1;//循环队列,大小开到2n+5
u=que[head];
exist[u]=0;//表示从队列取出
for(int i=head1[u];i;i=edge1[i].next){
v=edge1[i].to,val=edge1[i].val;
if(dis[v]>dis[u]+val){
dis[v]=dis[u]+val;
if(!exist[v]){//加入队列
++tail;
tail=(tail-1)%(MaxN*2+5)+1;
que[tail]=v;
exist[v]=1;
}
}
}
}
}
int ins[MaxN][52];
int dpp(int u,int k){
if(k<0||k>K)return 0;
if(ins[u][k]){//出现0环
ins[u][k]=0;
return -1;
}
if(dp[u][k]!=-1)return dp[u][k];
ins[u][k]=1;
int ans=0;
for(int i=head2[u];i;i=edge2[i].next){
int v=edge2[i].to,val=edge2[i].val;
if(dis[v]>dis[n]+K)continue;//剪枝
int tmp=dpp(v,dis[u]-dis[v]+k-val);
if(tmp==-1){
ins[u][k]=0;
return -1;
}
ans=(ans+tmp)%p;
}
ins[u][k]=0;
dp[u][k]=ans;
return ans;
}
inline void inin(){
memset(head1,0,sizeof(head1));
memset(head2,0,sizeof(head2));
memset(exist,0,sizeof(exist));
memset(dp,-1,sizeof(dp));//不能初始化为0。因为0边
tot=cns=0;
memset(dis,0x3f,sizeof(dis));
memset(ins,0,sizeof(ins));
}
inline int work(){
inin();
n=read();m=read();K=read();p=read();
for(int x,y,z,i=1;i<=m;i++){
x=read();y=read();z=read();
add1(x,y,z);
add2(y,x,z);
}
spfa();
int ans=0;
dp[1][0]=1;
for(int t=0;t<=K;t++){
int res=dpp(n,t);
if(res==-1) return -1;
ans=(ans+res)%p;
}
return ans;
}
int main(){
T=read();
while(T--) printf("%d\n",work());
return 0;
}