题意:求 dis(1,n)<=MinDis(1,n)+K d i s ( 1 , n ) <= M i n D i s ( 1 , n ) + K 的路径数
首先你可以考虑到 Day1 D a y 1 的 DP D P 去哪里了?
没错,你只要再注意一下 K≤50 K ≤ 50 就大概能想到这是一个与 k k 有关的 DP D P 了
右转LuoguP1608路径统计(P1144最短路计数可以顺带A掉)
设 dis1u d i s 1 u 表示1到u的最短路, disnu d i s n u 表示 u u 到 n n 的最短路(这个可以建反图跑出来)
考虑 f[u][j] f [ u ] [ j ] 表示 dis(1,u)≤dis1u+j d i s ( 1 , u ) ≤ d i s 1 u + j 的路径数
那么对于 edge(u,v,w) e d g e ( u , v , w )
那么从 1−>u−>v 1 − > u − > v 这条路径的长度就是 dis1u+j+w−dis1v d i s 1 u + j + w − d i s 1 v
如果 dis1u+w−dis1v+j≤K d i s 1 u + w − d i s 1 v + j ≤ K
就可以从 f[u][j] f [ u ] [ j ] 转移到 f[v][dis1u+j+w−dis1v] f [ v ] [ d i s 1 u + j + w − d i s 1 v ]
所以直接先从 1 1 跑最短路然后直接 O(KM)DP O ( K M ) D P 就 ok o k 了
当然这样还是有问题的
因为我们必须要先更新 dis1 d i s 1 小的点
所以要先按照 dis1 d i s 1 排个序再去转移就有 70pts 70 p t s 了
对于有 0 0 边的图,显然直接按照 dis1 d i s 1 去排序是不行的
例如 a−>b−>c,w=0 a − > b − > c , w = 0
因为他的一个有向图,更新顺序显然是 a,b,c a , b , c
所以考虑拓扑排序来确定 0 0 边两个端点的更新顺序
即对于 0 0 边,把其加入新图,然后对于新图拓扑排序确定” 0 0 点”的更新顺序
然后对于 −1 − 1 的情况显然是对于一条满足条件的路径上有一个 0 0 环
拓扑排序完了且入度不等于 0 0 说明这个点在 0 0 环上
同一个 0 0 环上任意一个点到 1 1 的最短路和到 n n 的最短路都一样
所以当这个点 i i 满足 dis1i+disni<=dis1n+K d i s 1 i + d i s n i <= d i s 1 n + K 时,就可以输出 −1 − 1 了
然后最后以 dis1 d i s 1 或 disn d i s n 为第一关键字,拓扑序为第二关键字排序再转移就 ok o k 了
这里是考场代码…最短路是用线段树跑的(线段树我目前我知道最快的,比斐波那契堆和配对堆什么的快多了,不信可以试试这道题)
#include
#include
#include
#include
#define re register int
#define fp(i,a,b) for(re i=a,I=b;i<=I;++i)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
using namespace std;
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
template<class T>inline void sdf(T&x){
char c;T y=1;while(c=gc(),(c<48||571)if(c=='-')y=-1;x=c^48;
while(c=gc(),4758)x=(x<<1)+(x<<3)+(c^48);x*=y;
}
using namespace std;
const int N=1e5+5,M=2e5+5;
typedef int arr[N];
struct eg{int nx,to,w;}e[M<<1],E[M];
int T,n,m,K,P,ce=1,Ce,ans,*dis,f[N][55];arr dis1,disn,fi1,fin,vis,in,q,Fi,id,da;
namespace seg{
int tr[N<<2],sgt;
inline void Set(re n){sgt=1;while(sgt<=n)sgt<<=1;--sgt;tr[0]=N-1;}
inline void clr(){fp(i,1,(sgt<<1)+1)tr[i]=0;}
inline int cmp(const re&a,const re&b){return dis[a]inline void mdy(re x,re w){for(re i=x+sgt;dis[tr[i]]>w;i>>=1)tr[i]=x;dis[x]=w;}
inline void del(re x){tr[x+=sgt]=0;x>>=1;while(x)tr[x]=cmp(tr[x<<1],tr[x<<1|1]),x>>=1;}
}
using namespace seg;
void dij(re s,re*Dis,re*fi){
dis=Dis;memset(dis,9,4*(n+1));clr();mdy(s,0);
fp(T,1,n){
re u=tr[1];del(u);
for(re i=fi[u],v;i;i=e[i].nx)
if(dis[v=e[i].to]>dis[u]+e[i].w)
mdy(v,dis[u]+e[i].w);
}
}
inline void add(re u,re v,re w,re*fi){e[++ce]=(eg){fi[u],v,w};fi[u]=ce;}
inline void ADD(re u,re v){E[++Ce]=(eg){Fi[u],v,0};Fi[u]=Ce;}
inline bool comp(const re&a,const re&b){return dis1[a]==dis1[b]?id[a]bool topsort(){
re h=1,t=0;
fp(i,1,n)if(!in[i])q[++t]=i;
while(h<=t){
re u=q[h++];
for(re i=Fi[u],v;i;i=E[i].nx)
if(!--in[v=E[i].to])q[++t]=v;
}
fp(i,1,n)id[q[i]]=i;
fp(i,1,n)if(in[i]&&dis1[i]+disn[i]<=K+dis1[n])return 1;
return 0;
}
inline void mod(re&a){a>=P?a-=P:0;}
int main(){
#ifndef ONLINE_JUDGE
file("park");
#endif
sdf(T);
while(T--){
memset(fi1,0,sizeof fi1);memset(fin,0,sizeof fin);
memset(Fi,0,sizeof Fi);ce=Ce=ans=0;
sdf(n);sdf(m);sdf(K);sdf(P);re u,v,w;Set(n);
while(m--){
sdf(u),sdf(v),sdf(w),add(u,v,w,fi1),add(v,u,w,fin);
if(!w)ADD(u,v),++in[v];
}
dij(1,dis1,fi1);dij(n,disn,fin);;
if(topsort()){puts("-1");continue;}
memset(f,0,sizeof f);f[1][0]=1;
fp(i,1,n)da[i]=i;sort(da+1,da+n+1,comp);
fp(k,0,K)fp(p,1,n)
for(re u=da[p],d=dis1[u],i=fi1[u];i;i=e[i].nx)
if(e[i].w-dis1[e[i].to]+d+k<=K)
mod(f[e[i].to][e[i].w-dis1[e[i].to]+d+k]+=f[u][k]);
fp(k,0,K)mod(ans+=f[n][k]);
printf("%d\n",ans);
}
return 0;
}
上面这个做法常数还不小,其实想想就知道,而且写起来很长
然后还有一种更加优美的做法就是记忆化搜索
只要跑一次反向的最短路
f[u][k] f [ u ] [ k ] 表示 dis(u,n)<=MinDis(u,n)+k d i s ( u , n ) <= M i n D i s ( u , n ) + k 的方案数,答案就是 f[1][K] f [ 1 ] [ K ]
考虑 egde(u,v,w) e g d e ( u , v , w )
同样的道理走这条边的话, dis(v,n)=MinDis(v,n)+w−MinDis(u,n) d i s ( v , n ) = M i n D i s ( v , n ) + w − M i n D i s ( u , n )
⇒f[u][k]=∑f[v][k−(MinDis(v,n)−MinDis(u,n)+w)] ⇒ f [ u ] [ k ] = ∑ f [ v ] [ k − ( M i n D i s ( v , n ) − M i n D i s ( u , n ) + w ) ]
这样怎么判 0 0 环呢?只要在搜索的时候记录个 instack i n s t a c k 就 ok o k 了
如果当前的 v v 还在搜索的栈中就可以直接返回 −1 − 1 了
#include
#include
#define re register int
#define fp(i,a,b) for(re i=a,I=b;i<=I;++i)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
template<class T>inline void sdf(T&x){
char c;T y=1;while(c=gc(),(c<48||571)if(c=='-')y=-1;x=c^48;
while(c=gc(),4758)x=(x<<1)+(x<<3)+(c^48);x*=y;
}
const int N=1e5+5,M=2e5+5;
typedef int arr[N];
struct eg{int nx,to,w;}e[M<<1];
int T,n,m,K,P,ce,f[N][51];arr dis,f1,fn;bool in[N][51];
namespace seg{
int tr[N<<2],sgt;
inline void Set(re n){sgt=1;while(sgt<=n)sgt<<=1;--sgt;tr[0]=N-1;}
inline void clr(){fp(i,1,(sgt<<1)+1)tr[i]=0;}
inline int cmp(const re&a,const re&b){return dis[a]inline void mdy(re x,re w){for(re i=x+sgt;dis[tr[i]]>w;i>>=1)tr[i]=x;dis[x]=w;}
inline void del(re x){tr[x+=sgt]=0;x>>=1;while(x)tr[x]=cmp(tr[x<<1],tr[x<<1|1]),x>>=1;}
}
using namespace seg;
void dij(){
memset(dis,9,4*(n+1));clr();mdy(n,0);
fp(T,1,n){
re u=tr[1];del(u);
for(re i=fn[u],v;i;i=e[i].nx)
if(dis[v=e[i].to]>dis[u]+e[i].w)
mdy(v,dis[u]+e[i].w);
}
}
inline void add(re u,re v,re w,re*fi){e[++ce]=(eg){fi[u],v,w};fi[u]=ce;}
inline void mod(re&a){a>=P?a-=P:0;}
int dfs(re u,re k){
if(in[u][k])return -1;
if(f[u][k])return f[u][k];
in[u][k]=1;u==n?f[u][k]=1:0;
for(re i=f1[u],v,w,tp;i;i=e[i].nx)
if((tp=dis[v=e[i].to]-dis[u]+e[i].w)<=k){
if((w=dfs(v,k-tp))==-1)return f[u][k]=-1;
mod(f[u][k]+=w);
}
return in[u][k]=0,f[u][k];
}
int main(){
#ifndef ONLINE_JUDGE
file("park");
#endif
sdf(T);
while(T--){
memset(f,0,sizeof f);memset(in,0,sizeof in);
sdf(n);sdf(m);sdf(K);sdf(P);Set(n);re u,v,w;
memset(f1,ce=0,4*(n+1));memset(fn,0,4*(n+1));
while(m--)sdf(u),sdf(v),sdf(w),add(u,v,w,f1),add(v,u,w,fn);
dij();printf("%d\n",dfs(1,K));
}
return 0;
}