专栏文章
题解:P3953 [NOIP2017 提高组] 逛公园
P3953题解参与者 7已保存评论 6
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 6 条
- 当前快照
- 1 份
- 快照标识符
- @miqg0mqy
- 此快照首次捕获于
- 2025/12/04 04:11 3 个月前
- 此快照最后确认于
- 2025/12/04 04:11 3 个月前
前言
可能是实现方法最详细的一片题解。我是黑暗贝利亚奥特曼,能踩的坑我都帮你们踩了。
题解
sub 1,2,7
sub 1,2,3,4,5,7,8
考虑边权不为 的情况,可以发现这是很好想的一个 DP。
具体来说,我们令 为 到 最短路径的长度。 表示当前节点为 ,路径长度为 ,这时图变成了一个 DAG,则我们可以写出转移式:
初始时 ,答案即为 。
该 DP 可以用记忆化搜索或拓扑排序实现。
显然,在边权不为 时,该 DP 不会有后效性。
正解
考虑在何种情况下答案会有无穷种。由刚才的 DP 可以发现,如果在 DP 时出现了一个由 构成的环时,会有无穷种答案。
考虑如何判断。
方案一:我们可以把所有边权为 的边建一张图,在上面跑 tarjan 缩点,这样就形成了一张 DAG。再在 DAG 上跑一遍拓扑,看 到 的路径上是否有大小超过 的强连通分量,如果有显然会有无穷种答案。
方案二:我们在跑记忆化搜索时,如果从一个点出发还能搜到这一个点,就会有无穷种答案。于是可以在搜索时用一个数组记录一个状态是否在搜索中,如果它已经被搜过就返回 ,这样就完成了判断。
几个坑点
由于本人太菜,在做题时错了很多次,在摸索的过程中才把正解探究出来。
-
在求 数组前要跑一遍 dij,且 数组不能直接在 dij 中求出来。
-
在记忆化搜索时,要判断 中的 是否满足 ,尤其是要判断 (感谢 @LLCCFF 的指出),这一种情况很容易漏掉。
-
如果你是记忆化搜索,在把 设为 前,先要搜索一下 是否有无穷种答案(其实样例就可以测出来)。
-
最根本的问题。检查你的 dij 是否出错(比如使用了大根堆、没有 数组)!
-
记得取模 。
-
多测清空(用样例应该也能测出来)。
代码
这份代码使用了上文中的记忆化搜索,记忆化搜索时的传参与 的状态设计稍有不同。
CPP#include<bits/stdc++.h>
using namespace std;
#define N 100010
int T,n,m,k,p,dis[N],f[N][51];
bool ins[N][51],fl,vis[N];
vector<pair<int,int>>to[N],to2[N];
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q; //注意是小根堆
int calc(int x,int ds){ //记忆化搜索
if(fl){ //如果有无穷种答案,直接返回
return 0;
}
if(ds<dis[x]){ //一定要注意!
return 0;
}
if(ds>dis[x]+k){
return 0;
}
if(ins[x][ds-dis[x]]){ //判断是否访问到自己
fl=1;
return 0;
}
if(f[x][ds-dis[x]]!=-1){ //记忆化,注意没有访问到的状态应设为 -1
return f[x][ds-dis[x]];
}
ins[x][ds-dis[x]]=1;
f[x][ds-dis[x]]=0;
for(auto [y,w]:to2[x]){
f[x][ds-dis[x]]=(f[x][ds-dis[x]]+calc(y,ds-w))%p; //记得取模
}
ins[x][ds-dis[x]]=0;
return f[x][ds-dis[x]];
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>T;
while(T--){
fl=0;
cin>>n>>m>>k>>p;
for(int i=1;i<=n;i++){ //多测要清空
to[i].clear();
to2[i].clear();
vis[i]=0;
}
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
to[u].push_back(make_pair(v,w));
to2[v].push_back(make_pair(u,w)); //记忆化搜索是在反图上进行
}
for(int i=1;i<=n;i++){
for(int j=0;j<=k;j++){
f[i][j]=-1;
}
dis[i]=1e10;
}
dis[1]=0;
q.push(make_pair(0,1));
while(!q.empty()){ //先求出最短路
int x=q.top().second;
q.pop();
if(vis[x]){
continue;
}
vis[x]=1;
for(auto [y,w]:to[x]){
if(dis[x]+w<dis[y]){
dis[y]=dis[x]+w;
q.push(make_pair(dis[y],y));
}
}
}
calc(1,0);
f[1][0]=1;
int ans=0;
for(int i=0;i<=k;i++){
ans=(ans+calc(n,dis[n]+i))%p; //统计答案
}
if(fl){
cout<<-1<<'\n'; //判断答案是否有无穷种
continue;
}
cout<<ans<<'\n';
}
}
相关推荐
评论
共 6 条评论,欢迎与作者交流。
正在加载评论...