[题解]P3953 [NOIP 2017 提高组] 逛公园
给定边权非负的有向图,记其最短路为 \(d\),求长度不超过 \(d+k\) 的路径总数,或报告有无穷多条。
\(n\le 10^5,m\le 2\times 10^5,k\le 50\)。
考虑到 \(k\) 很小,我们令 \(f[u][i]\) 为 \(1\to u\),比最短路 \(d[u]\) 多走了 \(k\) 的长度的方案数。
则有转移:
\[f[u][i]=\sum\limits_{(v,u)\in E} f[v][x]
\]
其中 \(d_u+i=d_v+x+w(u,v)\),即 \(x=d_u+i-d_v-w(u,v)\)。
若符合条件的路径上没有零权环,这样子是没有后效性的。
反之,若有零权环,则说明有无穷多条路径。可以在转移 \(f\) 的过程中完成判定。
总时间 \(O(m\log m+n)\)。瓶颈在于 Dijkstra。
点击查看代码
#include<bits/stdc++.h>
#define eb emplace_back
#define PII pair<int,int>
#define int long long
using namespace std;
const int N=1e5+5,K=52;
int t,n,m,k,P,f[N][K],d[N];
struct Ed{int to,w;};
bool flg;
vector<Ed> G[N],G2[N];
bitset<N> vis;
bitset<K> in[N];
priority_queue<PII,vector<PII>,greater<PII>> q;
inline void dij(int s){
memset(d,0x3f,sizeof d);
d[s]=0,q.push({0,s});
while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(Ed i:G[u]){
int v=i.to,w=i.w;
if(d[u]+w<d[v]){
d[v]=d[u]+w;
q.push({d[v],v});
}
}
}
}
inline int dfs(int u,int dd){//dd: 到u的路径长度
int ex=dd-d[u],s=0;//额外走了多少
if(ex<0||ex>k) return 0;
if(in[u][ex]) return flg=1,0;
if(~f[u][ex]) return f[u][ex];
in[u][ex]=1;
for(Ed i:G2[u]){
int v=i.to,w=i.w,x=dfs(v,dd-w);
if(flg) return in[u][ex]=0;
(s+=x)%=P;
}
in[u][ex]=0;
if(u==1&&!ex) s=1;//为了找环 需要在这里赋初值
return f[u][ex]=s;
}
inline void clr(){
vis=flg=0;
for(int i=1;i<=n;i++) G[i].clear(),G2[i].clear();
memset(f,-1,sizeof f);
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>t;
while(t--){
cin>>n>>m>>k>>P;
clr();
for(int i=1,u,v,w;i<=m;i++){
cin>>u>>v>>w;
G[u].eb(Ed{v,w});
G2[v].eb(Ed{u,w});
}
dij(1);
int ans=0;
for(int i=0;i<=k;i++) (ans+=dfs(n,d[n]+i))%=P;
if(flg) cout<<"-1\n";
else cout<<ans<<"\n";
}
return 0;
}
浙公网安备 33010602011771号