题目:洛谷P3953、Vijos P2030、UOJ#331。
题目大意:
给你(N)点(M)边的有向无权图,没有负权但有零权。求(1)到(N)的路径中长度不超过(d+K)的路径的条数(mod P),无穷输出(-1)。
解题思路:
考场上我写的貌似接近正解的dp,只是先判了零环,然后传递值的时候比较暴力,疯狂优化常数后拿了70分。
正解也是dp。
设(f_{i,j})表示到(i)号点,比最短路径多(j)的路径条数,(d_i)表示到(i)的最短路径长度。
则(f_{i,j}=sum f_{p,d_p-d_i-e+j} )((e)为(p)到(i)的一条边权长,且({p,d_p-d_i-e+j}geqslant 0))
边界(f_{0,0}=1)(需要加一条(0)到(1)的零边并作为起点,否则样例的零环会挂)。
我们可以用记忆化搜索计算(f),当状态({i,j})出现了两次,则一定出现了零环,退出输出(-1)即可。
时间复杂度貌似有点玄学,但还是过了(最坏(O(NK^2))?)。
C++ Code:
#include<bits/stdc++.h> #define M 200005 #define N 100005 inline int readint(){ int c=getchar(),d=0; for(;!isdigit(c);c=getchar()); for(;isdigit(c);c=getchar()) d=(d<<3)+(d<<1)+(c^'0'); return d; } struct edge{ int to,dis,nxt; }e[M],e2[M]; int head[N],head2[N],n,m,k,p,cnt1,cnt2,dis[N],l,r,q[130005]; int dp[N][52]; bool vis[N],ur[N][52],ok; inline int add1(const int u,const int v,const int t){ e[++cnt1]=(edge){v,t,head[u]}; head[u]=cnt1; } inline int add2(const int u,const int v,const int t){ e2[++cnt2]=(edge){u,t,head2[v]}; head2[v]=cnt2; } int dfs(int u,int k){ if(~dp[u][k])return dp[u][k]; dp[u][k]=0,ur[u][k]=1; for(int i=head2[u];i!=-1&&ok;i=e2[i].nxt){ int v=e2[i].to; int d=dis[u]-dis[v]-e2[i].dis+k; if(d<0)continue; if(ur[v][d]){ ok=0;return 0; } dp[u][k]=(dp[u][k]+dfs(v,d))%p; } ur[u][k]=0; return dp[u][k]; } int main(){ for(int T=readint();T--;){ memset(head,-1,sizeof head); memset(head2,-1,sizeof head2); memset(e,0,sizeof e); memset(e2,0,sizeof e2); cnt1=cnt2=0; n=readint(),m=readint(),k=readint(),p=readint(); while(m--){ int u=readint(),v=readint(),d=readint(); add1(u,v,d); add2(u,v,d); } add1(0,1,0),add2(0,1,0); memset(dis,0x3f,sizeof dis); memset(vis,0,sizeof vis); dis[0]=0; l=q[1]=0,r=vis[0]=1;int u; while(l!=r){ vis[u=q[l=l%130000+1]]=0; for(int i=head[u];i!=-1;i=e[i].nxt) if(dis[e[i].to]>dis[u]+e[i].dis){ dis[e[i].to]=dis[u]+e[i].dis; if(!vis[e[i].to])vis[q[r=r%130000+1]=e[i].to]=1; } } memset(dp,-1,sizeof dp); memset(ur,0,sizeof ur); ok=dp[0][0]=1; int ans=0; for(int i=0;i<=k&&ok;++i)ans=(ans+dfs(n,i))%p; printf("%d ",ok?ans:-1); } return 0; }