题目:洛谷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\n",ok?ans:-1);
}
return 0;
}