当前位置: 首页 > news >正文

洛谷P3953 [NOIP 2017 提高组] 逛公园

洛谷P3953 [NOIP 2017 提高组] 逛公园

洛谷题目传送门

题目背景

NOIP2017 D1T3

题目描述

策策同学特别喜欢逛公园。公园可以看成一张 N N N 个点 M M M 条边构成的有向图,且没有 自环和重边。其中 1 1 1 号点是公园的入口, N N N 号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。

策策每天都会去逛公园,他总是从 1 1 1 号点进去,从 N N N 号点出来。

策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果 1 1 1 号点 到 N N N 号点的最短路长为 d d d,那么策策只会喜欢长度不超过 d + K d + K d+K 的路线。

策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?

为避免输出过大,答案对 P P P 取模。

如果有无穷多条合法的路线,请输出 − 1 -1 1

输入格式

第一行包含一个整数 T T T, 代表数据组数。

接下来 T T T 组数据,对于每组数据: 第一行包含四个整数 N , M , K , P N,M,K,P N,M,K,P,每两个整数之间用一个空格隔开。

接下来 M M M 行,每行三个整数 a i , b i , c i a_i,b_i,c_i ai,bi,ci,代表编号为 a i , b i a_i,b_i ai,bi 的点之间有一条权值为 c i c_i ci 的有向边,每两个整数之间用一个空格隔开。

输出格式

输出文件包含 T T T 行,每行一个整数代表答案。

输入输出样例 #1

输入 #1

2
5 7 2 10
1 2 1
2 4 0
4 5 2
2 3 2
3 4 1
3 5 2
1 5 3
2 2 0 10
1 2 0
2 1 0

输出 #1

3
-1

说明/提示

【样例解释1】

对于第一组数据,最短路为 3 3 3 1 → 5 , 1 → 2 → 4 → 5 , 1 → 2 → 3 → 5 1\to 5, 1\to 2\to 4\to 5, 1\to 2\to 3\to 5 15,1245,1235 3 3 3 条合法路径。

【测试数据与约定】

对于不同的测试点,我们约定各种参数的规模不会超过如下

测试点编号 T T T N N N M M M K K K是否有 0 0 0
1 1 1 5 5 5 5 5 5 10 10 10 0 0 0
2 2 2 5 5 5 10 3 10^3 103 2 × 10 3 2\times 10^3 2×103 0 0 0
3 3 3 5 5 5 10 3 10^3 103 2 × 10 3 2\times 10^3 2×103 50 50 50
4 4 4 5 5 5 10 3 10^3 103 2 × 10 3 2\times 10^3 2×103 50 50 50
5 5 5 5 5 5 10 3 10^3 103 2 × 10 3 2\times 10^3 2×103 50 50 50
6 6 6 5 5 5 10 3 10^3 103 2 × 10 3 2\times 10^3 2×103 50 50 50
7 7 7 5 5 5 10 5 10^5 105 2 × 10 5 2\times 10^5 2×105 0 0 0
8 8 8 3 3 3 10 5 10^5 105 2 × 10 5 2\times 10^5 2×105 50 50 50
9 9 9 3 3 3 10 5 10^5 105 2 × 10 5 2\times 10^5 2×105 50 50 50
10 10 10 3 3 3 10 5 10^5 105 2 × 10 5 2\times 10^5 2×105 50 50 50

对于 100 % 100\% 100% 的数据, 1 ≤ P ≤ 10 9 1 \le P \le 10^9 1P109 1 ≤ a i , b i ≤ N 1 \le a_i,b_i \le N 1ai,biN 0 ≤ c i ≤ 1000 0 \le c_i \le 1000 0ci1000

数据保证:至少存在一条合法的路线。


  • 2019.8.30 增加了一组 hack 数据 by @skicean
  • 2022.7.21 增加了一组 hack 数据 by @djwj233

思路详解

最短路

首先由于它要求最短路径,由于害怕被卡,所以保险起见我们采用dijkstra求解最短路。

void dij(ll o){//dijkstra标准模板,o为起点memset(dis,0x3f,sizeof(dis));//记得赋初值!!!memset(vis,0,sizeof(vis));queue<ll>q;q.push(o);vis[o]=1;dis[o]=0;while(!q.empty()){ll u=q.front();q.pop();vis[u]=0;for(ll i=h[u];i;i=ne[i]){ll v=to[i];if(dis[v]>dis[u]+w[i]){dis[v]=dis[u]+w[i];if(!vis[v]){vis[v]=1;q.push(v);}}}}
}

深搜求解

我们最多有 k k k的相差,考虑依次枚举实际相差的 q q q(否则可能计算不完全),采用记忆化深搜求解。具体步骤如下:

  1. 定义:记忆化数组 f u , j f_{u,j} fu,j为到 u u u点, q q q剩下 j j j的方案数。 d f s ( u , q ) dfs(u,q) dfs(u,q)同理
  2. 边界条件: f n , 0 = 1 f_{n,0}=1 fn,0=1
  3. 状态转移:令 d = q − ( d i s u + w i − d i s v ) d=q-(dis_{u}+w_{i}-dis_{v}) d=q(disu+widisv)即消耗了当前的 q q q后的值,若 d < 0 d<0 d<0则直接退出。
    否则累加答案: f u , j + = d f s ( v , d ) f_{u,j}+=dfs(v,d) fu,j+=dfs(v,d)

但是,我们发现,题目中说可能有无数解的情况,肯定是有0环,那如何判断是否有0环呢?,考虑定于数组 v i u , q vi_{u,q} viu,q,表示是否访问过状态 u , q {u,q} u,q,若在深搜访问状态 u , q {u,q} u,q v i u , q = = 1 vi_{u,q}==1 viu,q==1,那说明跑了一圈跑回来了,那一定有0环,标记并退出。

**int f[N][56],fl=0,vi[N][56];
int dfs(int u,int q){if(vi[u][q]){//已经访问过这种状态,有0环fl=1;return 0;}if(f[u][q]!=-1)return f[u][q];//记忆化深搜vi[u][q]=1;//标记f[u][q]=0;//由于f数组初始值为-1,将其赋值为0for(int i=h[u];i;i=ne[i]){int v=to[i];int d=q-(dis[u]+w[i]-dis[v]);//消耗后的状态if(d<0)continue;f[u][q]=(f[u][q]+dfs(v,d))%p;//求和,记得取模if(fl==1)return 0;//dfs(v,d)中fl变成了1也要退出}if(u==n&&q==0)f[u][q]=1;//边界vi[u][q]=0;//回溯return f[u][q];
}**

反向跑图

我们将代码提交上去,发现有些数据是错的。而且错误数据都是错误输出-1,即0环判断错误。这是为何?我们发现,如果一旦有0环我们的深搜便会返回-1,但其实有可能根本不会经过这个0环,那如何规避这个错误呢?我们直接反向跑图,这样乱跑的情况就会被规避掉。但要注意,状态转移变为了: d = q − ( d i s v + w i − d i s u ) d=q-(dis_{v}+w_{i}-dis_{u}) d=q(disv+widisu),因为原来是 v − > u v->u v>u,现在变为了 u − > v u->v u>v

完整代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2e5+4;
ll n,m,t,k,p;
ll h[N],to[N],w[N],ne[N],tot;
void add(ll u,ll v,ll d){tot++;to[tot]=v;w[tot]=d;ne[tot]=h[u];h[u]=tot;
}
struct edge{ll x,y,z;
}a[N];
ll dis[N],vis[N];
void dij(ll o){//dijkstra标准模板,o为起点memset(dis,0x3f,sizeof(dis));//记得赋初值!!!memset(vis,0,sizeof(vis));queue<ll>q;q.push(o);vis[o]=1;dis[o]=0;while(!q.empty()){ll u=q.front();q.pop();vis[u]=0;for(ll i=h[u];i;i=ne[i]){ll v=to[i];if(dis[v]>dis[u]+w[i]){dis[v]=dis[u]+w[i];if(!vis[v]){vis[v]=1;q.push(v);}}}}
}
ll ans;
ll f[N][56],fl=0,vi[N][56];
ll dfs(ll u,ll q){if(vi[u][q]){//已经访问过这种状态,有0环fl=1;return 0;}if(f[u][q]!=-1)return f[u][q];//记忆化深搜vi[u][q]=1;//标记f[u][q]=0;//由于f数组初始值为-1,将其赋值为0for(ll i=h[u];i;i=ne[i]){ll v=to[i];ll d=q-(dis[v]+w[i]-dis[u]);//消耗后的状态if(d<0)continue;f[u][q]=(f[u][q]+dfs(v,d))%p;//求和,记得取模if(fl==1)return 0;//dfs(v,d)中fl变成了1也要退出}if(u==1&&q==0)f[u][q]=1;//边界vi[u][q]=0;//回溯return f[u][q];
}
int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>t;for(ll cas=1;cas<=t;cas++){cin>>n>>m>>k>>p;fl=0;tot=0;//链式前向星清空只需要清空tot与hmemset(h,0,sizeof(h));for(ll i=1;i<=m;i++){ll x,y,z;cin>>x>>y>>z;a[i]={x,y,z};//将边保存下来}for(ll i=1;i<=m;i++){ll x=a[i].x,y=a[i].y,z=a[i].z;add(x,y,z);//正向建边}dij(1);//正向跑最短路ans=0;tot=0;//清空memset(h,0,sizeof(h));for(ll i=1;i<=m;i++){//方向见图ll x=a[i].x,y=a[i].y,z=a[i].z;add(y,x,z);}memset(f,-1,sizeof(f));memset(vi,0,sizeof(vi));for(ll i=0;i<=k;i++){ans=(ans+dfs(n,i))%p;if(fl)break;}if(fl)cout<<-1<<'\n';else cout<<ans<<'\n';}return 0;
}
http://www.lqws.cn/news/471691.html

相关文章:

  • c++11标准(5)——并发库(互斥锁)
  • Spring面向切面编程AOP(2)
  • Android Studio 打 APK 包报错 Invalid keystore format 的解决方法
  • Vue3 + TypeScript 中 let data: any[] = [] 与 let data = [] 的区别
  • 【力扣 简单 C】509. 斐波那契数
  • “组学”的数据结构与概念
  • 恒流源和直流稳压电源 电路
  • 【Linux】gdb调试器
  • 蓝桥杯备赛篇(上) - 参加蓝桥杯所需要的基础能力 1(C++)
  • 偏微分方程通解求解2
  • 【RAG优化】深度解析开源项目MinerU:从PDF解析到多模态理解的工业级解决方案
  • 正则表达式与C++
  • 【Java】APi
  • rt-thread中使用usb官方自带的驱动问题记录
  • Compose笔记(二十八)--加水印
  • 【好用但慎用】Windows 系统中将所有 WSL 发行版从 C 盘迁移到 非系统 盘的完整笔记(附 异常处理)
  • 网络基础入门:从OSI模型到TCP/IP协议详解
  • Gartner《AI-Driven Methods for Cost-Efficiency》学习心得
  • SQL Server 数据库操作
  • 大模型的开发应用(十二):RAG 与 LlamaIndex基础
  • 【论文阅读】人工智能在直升机航空电子系统中的应用
  • 随机一道面试题1:Python是解释型语言or编译型语言?
  • 算法-Day04
  • SD-WAN 不是“裸跑”:聊聊怎么把网络安全绑在智能网关上
  • 2025zbrush雕刻笔记
  • DPO直接偏好函数的学习解读
  • C语言:最大公约数
  • 以AI赋能创意未来:即梦3.0与Seedance1.0Lite重磅登陆POE!
  • 操作系统内核态和用户态--2-系统调用是什么?
  • 新手如何利用AI助手Cursor生成复杂项目