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

信息学奥赛一本通 1505:【例 2】双调路径 | 洛谷 P5530 [BalticOI 2002] 双调路径

【题目链接】

ybt 1505:【例 2】双调路径
洛谷 P5530 [BalticOI 2002] 双调路径

【题目考点】

1. 图论:最短路
  • Dijkstra算法
  • SPFA算法
2. 动态规划:图上动规
3. 树状数组

【解题思路】

无向图,图中每条边的权值包括两种:费用c和时间t。

根据题意,一条路径比另一条路径更“小”的定义如下:
路径1上各边的费用加和为 C 1 C_1 C1,时间加和为 T 1 T_1 T1
路径2上各边的费用加和为 C 2 C_2 C2,时间加和为 T 2 T_2 T2
如果 C 1 < C 2 C_1 < C_2 C1<C2同时 T 1 < T 2 T_1 < T_2 T1<T2,或 C 1 = C 2 C_1 = C_2 C1=C2同时 T 1 < T 2 T_1 < T_2 T1<T2,或 C 1 < C 2 C_1 < C_2 C1<C2同时 T 1 = T 2 T_1 = T_2 T1=T2,那么路径1优于路径2。
与该逻辑等价的逻辑表达式为 ( C 1 < C 2 ∧ T 1 ≤ T 2 ) ∨ ( C 1 ≤ C 2 ∧ T 1 < T 2 ) (C_1<C_2\land T_1\le T_2) \lor (C_1 \le C_2 \land T_1<T_2) (C1<C2T1T2)(C1C2T1<T2)( ∧ \land 表示与, ∨ \lor 表示或)
如不满足条件,就不能对路径1与路径2哪条路径更小做出判定。
该概念即为帕累托最优。
本题求从源点s出发到终点e的最“小”的路径的数量。也就是说,从源点s到终点e的所有其它路径中,找不到比最“小”路径更“小”的路径。
由于每条边的时间或费用最大为100,总顶点数为100,则最小路径总边数不超过100,路径的时间或费用最大不超过 100 ∗ 100 = 10000 100*100=10000 100100=10000,大小不超过 10000 10000 10000的整数可以作为数组下标。

以下考虑动态规划

状态定义
  • 阶段:到达顶点编号i,路径费用j
    (或者此处也可以选择使用取路径时间,本题中费用和时间是等价的)
  • 决策:从某顶点出发下一步到哪个顶点
  • 策略:路径
  • 策略集合:从源点s出发到顶点i的路径费用为j的所有路径
  • 条件:时间最小
    在路径费用固定的情况下,只有时间最小的路径才可能是最“小”路径,时间更大的路径没有必要统计。因此只需要统计费用固定的所有路径中时间最小的路径的最小时间。
  • 统计量:时间
    状态定义 d p i , j dp_{i,j} dpi,j:从源点s出发到顶点i的路径费用为j的所有路径中,时间最小的路径的时间。
    初始状态:
    源点s到任意顶点的路径是否存在还未知,将dp数组初值都设为无穷大INF,表示先认为源点s到其它顶点的路径都不存在。
    源点s到s存在路径费用为0,时间为0的一条路径,所以 d p s , 0 = 0 dp_{s,0} = 0 dps,0=0
状态转移方程
  • 策略集合:从源点s出发到顶点u的路径费用为uc的所有路径
  • 分割策略集合:根据有哪些顶点u通过一条边可以到达顶点v来分割策略集合

对于所有到顶点v有边的顶点u,到达顶点u路径费用为uc的路径中时间最小的路径的时间为 d p u , u c dp_{u, uc} dpu,uc,经过一条费用为c,时间为t的边后到达顶点v,到达顶点v时路径的费用为 v c = u c + c vc=uc+c vc=uc+c,时间为 d p u , u c + t dp_{u, uc}+t dpu,uc+t
如果该时间 d p u , u c + t dp_{u, uc}+t dpu,uc+t比原来到达顶点v路径费用为 v c vc vc的时间最小的路径的时间更小,则更新该到达顶点v费用为 v c vc vc的最小的路径的时间 d p v , v c dp_{v, vc} dpv,vc d p u , u c + t dp_{u, uc}+t dpu,uc+t

由于顶点v的状态值发生更新了,表示存在一条从源点到顶点v的费用为 v c vc vc的时间更小的路径,接下来还要从顶点v出发更新它的邻接点。这一过程可以使用SPFA算法,或Dijkstra堆优化算法(将顶点v加入优先队列)完成。队列或优先队列中保存路径到达顶点u,以及路径费用c。

解法1:SPFA算法

只要顶点v的状态发生更新,就将顶点v,以及到顶点v的路径的费用vc合成一个路径对象加入队列。
inQue设为二维数组,inQue[i][j]表示到达顶点i费用为j的路径是否在队列内。

解法2:Dijkstra算法

路径对象包括到达顶点u,路径费用uc,路径时间ut。路径时间t小的路径更优先。
如果出队的路径的时间t已经大于 d p u , u c dp_{u, uc} dpu,uc,那么就没有必要尝试更新顶点u的连接点的状态。

最后根据dp数组统计出最“小”路径的数量。如果已经存在一条到达终点e,费用为ec,时间为et的最小路径,那么对于到达顶点e费用大于ec的路径,其时间必须小于et,这样的路径才是最小路径。因此当路径费用不断增大时,路径的时间必须小于上一次选出的最小路径的时间,该路径才是一个最小路径。
设minTime为上一次选择的路径的最小时间,初值设为INF。 j j j表示路径费用,从0到10000循环。ans表示最小路径的数量。
如果发现 d p e , j < m i n T i m e dp_{e,j} < minTime dpe,j<minTime,则到达顶点e,费用为j,时间为 d p e , j dp_{e,j} dpe,j的路径也是一条最小路径,最小路径数量ans增加1。
最后输出最小路径数量ans。

以上逻辑写出的代码已经可以通过本题,不过还可以进行进一步优化。

【优化】树状数组优化

对于一个从源点s出发,通过顶点u到达顶点v,费用为 v c vc vc,时间为 d p u , u c + t dp_{u, uc}+t dpu,uc+t的路径,根据上述找最小路径的思路,如果该路径的时间大于等于到达顶点v的费用小于等于 v c vc vc的任意路径的时间,则该路径一定不是最优路径。因此只有该路径时间 d p u , u c + t dp_{u,uc}+t dpu,uc+t满足小于所有 顶点费用小于等于 v c vc vc的每个路径的最小时间 的最小值,也就是 m i n 0 ≤ j ≤ v c { d p v , j } min_{0\le j\le vc}\{dp_{v,j}\} min0jvc{dpv,j},才需要更新状态 d p v , v c dp_{v, vc} dpv,vc
遍历求最小值一定会超时,此处是求一个数组前j个元素的最小值,该最小值可以使用树状数组维护,每次更新和查询都是 O ( l o g n ) O(logn) O(logn)复杂度。
注意:树状数组下标从1开始,因此传入的值需要先加1后再进行更新或查询。

【题解代码】

解法1:图上动规 基本做法

  • SPFA算法
#include<bits/stdc++.h>
using namespace std;
#define N 105
#define M 10005
#define INF 0x3f3f3f3f
struct Edge
{int v, c, t;//到顶点v,费用c,时间t 
};
struct Path
{int u, c;
};
vector<Edge> edge[N];
int n, m, s, e, ans, dp[N][M], minTime = INF;
bool inQue[N][M];
void spfa(int sv)
{memset(dp, 0x3f, sizeof(dp));queue<Path> que;dp[sv][0] = 0;//dp[i][j]:从源点到顶点i使用费用为j的路径的最小时间que.push(Path{sv, 0});inQue[sv][0] = true;while(!que.empty()){ int u = que.front().u, uc = que.front().c;//存在一条从源点到u的花费为uc的路径。que.pop();inQue[u][uc] = false;for(Edge e : edge[u]){int v = e.v, c = e.c, t = e.t, vc = uc+c;if(vc <= 10000 && dp[v][vc] > dp[u][uc]+t){dp[v][vc] = dp[u][uc]+t;if(!inQue[v][vc]){inQue[v][vc] = true;que.push(Path{v, vc});}}}}
}
int main()
{int p, r, c, t;cin >> n >> m >> s >> e;for(int i = 1; i <= m; ++i){cin >> p >> r >> c >> t;edge[p].push_back(Edge{r, c, t});edge[r].push_back(Edge{p, c, t});}spfa(s);for(int j = 0; j <= 10000; ++j) if(dp[e][j] < minTime){ans++;minTime = dp[e][j];}cout << ans;return 0;
}
  • Dijkstra堆优化算法
#include<bits/stdc++.h>
using namespace std;
#define N 105
#define M 10005
#define INF 0x3f3f3f3f
struct Edge
{int v, c, t;//到顶点v,费用c,时间t 
};
struct Path
{int u, c, t;bool operator < (const Path &b) const{return b.t < t;}
};
vector<Edge> edge[N];
int n, m, s, e, ans, dp[N][M], minTime = INF;
void dijkstra(int sv)
{memset(dp, 0x3f, sizeof(dp));priority_queue<Path> que;dp[sv][0] = 0;//dp[i][j]:从源点到顶点i使用费用为j的路径的最小时间que.push(Path{sv, 0, 0});while(!que.empty()){int u = que.top().u, uc = que.top().c, ut = que.top().t;que.pop();if(dp[u][uc] < ut)//该路径已经不是最优的,则跳过 continue;for(Edge e : edge[u]){int v = e.v, c = e.c, t = e.t, vc = uc+c;if(vc <= 10000 && dp[v][vc] > dp[u][uc]+t)//注意:vc=uc+c可能超过10000,导致数组越界 {dp[v][vc] = dp[u][uc]+t;que.push(Path{v, vc, dp[v][vc]});}}}
}
int main()
{int p, r, c, t;cin >> n >> m >> s >> e;for(int i = 1; i <= m; ++i){cin >> p >> r >> c >> t;edge[p].push_back(Edge{r, c, t});edge[r].push_back(Edge{p, c, t});}dijkstra(s);for(int j = 0; j <= 10000; ++j) if(dp[e][j] < minTime){ans++;minTime = dp[e][j];}cout << ans;return 0;
}
解法2:图上动规+树状数组优化
  • SPFA算法
#include<bits/stdc++.h>
using namespace std;
#define N 105
#define M 10005
#define INF 0x3f3f3f3f
struct Edge
{int v, c, t;//到顶点v,费用c,时间t 
};
struct Path
{int u, c;
};
vector<Edge> edge[N];
int n, m, s, e, ans, dp[N][M], minTime = INF, tree[N][M];
bool inQue[N][M];
int lowbit(int x)
{return x & -x;
}
void update(int i, int j, int v)//dp[i][j] = v
{for(int x = j+1; x < M; x += lowbit(x))tree[i][x] = min(tree[i][x], v);
}
int query(int i, int j)//min{dp[i][0]~dp[i][j]}
{int res = INF;for(int x = j+1; x > 0; x -= lowbit(x))res = min(res, tree[i][x]);return res; 
}
void spfa(int sv)
{memset(dp, 0x3f, sizeof(dp));memset(tree, 0x3f, sizeof(dp));queue<Path> que;dp[sv][0] = 0;//dp[i][j]:从源点到顶点i使用费用为j的路径的最小时间que.push(Path{sv, 0});inQue[sv][0] = true;while(!que.empty()){ int u = que.front().u, uc = que.front().c;//存在一条从源点到u的花费为uc的路径。que.pop();inQue[u][uc] = false;for(Edge e : edge[u]){int v = e.v, c = e.c, t = e.t, vc = uc+c;if(vc <= 10000 && query(v, vc) > dp[u][uc]+t){dp[v][vc] = dp[u][uc]+t;update(v, vc, dp[u][uc]+t);if(!inQue[v][vc]){inQue[v][vc] = true;que.push(Path{v, vc});}}}}
}
int main()
{int p, r, c, t;cin >> n >> m >> s >> e;for(int i = 1; i <= m; ++i){cin >> p >> r >> c >> t;edge[p].push_back(Edge{r, c, t});edge[r].push_back(Edge{p, c, t});}spfa(s);for(int j = 0; j <= 10000; ++j) if(dp[e][j] < minTime){ans++;minTime = dp[e][j];}cout << ans;return 0;
}
  • Dijkstra堆优化算法
#include<bits/stdc++.h>
using namespace std;
#define N 105
#define M 10005
#define INF 0x3f3f3f3f
struct Edge
{int v, c, t;//到顶点v,费用c,时间t 
};
struct Path
{int u, c, t;bool operator < (const Path &b) const{return b.t < t;}
};
vector<Edge> edge[N];
int n, m, s, e, ans, dp[N][M], tree[N][M], minTime = INF;
int lowbit(int x)
{return x & -x;
}
void update(int i, int j, int v)//dp[i][j] = v
{for(int x = j+1; x < M; x += lowbit(x)) tree[i][x] = min(tree[i][x], v);
}
int query(int i, int j)//min{dp[i][0]~dp[i][j]}
{int res = INF;for(int x = j+1; x > 0; x -= lowbit(x))res = min(res, tree[i][x]);return res; 
}
void dijkstra(int sv)
{memset(dp, 0x3f, sizeof(dp));	memset(tree, 0x3f, sizeof(dp));priority_queue<Path> que;dp[sv][0] = 0;//dp[i][j]:从源点到顶点i使用费用为j的路径的最小时间que.push(Path{sv, 0, 0});while(!que.empty()){int u = que.top().u, uc = que.top().c, ut = que.top().t;que.pop();if(dp[u][uc] < ut)//该路径已经不是最优的,则跳过 continue;for(Edge e : edge[u]){int v = e.v, c = e.c, t = e.t, vc = uc+c;if(vc <= 10000 && query(v, vc) > dp[u][uc]+t)//注意:vc=uc+c可能超过10000,导致数组越界 {dp[v][vc] = dp[u][uc]+t;update(v, vc, dp[u][uc]+t);que.push(Path{v, vc, dp[v][vc]});}}}
}
int main()
{int p, r, c, t;cin >> n >> m >> s >> e;for(int i = 1; i <= m; ++i){cin >> p >> r >> c >> t;edge[p].push_back(Edge{r, c, t});edge[r].push_back(Edge{p, c, t});}dijkstra(s);for(int j = 0; j <= 10000; ++j) if(dp[e][j] < minTime){ans++;minTime = dp[e][j];}cout << ans;return 0;
}

相关文章:

  • 03-谷粒商城笔记
  • MongoDB(docker版)备份还原
  • 八大排序——选择排序/堆排序
  • Android APP 爬虫操作
  • 海外产能达产,威尔高一季度营收利润双双大增
  • 【k8s】docker、k8s、虚拟机的区别以及使用场景
  • shell脚本1
  • SwiftUI 常用控件简介
  • Hi3518E官方录像例程源码流程分析(五)
  • PNG透明免抠设计素材大全26000+
  • python异步协程async调用过程图解
  • HTTP 请求头的 key 不区分大小写。
  • FlinkUDF用户自定义函数深度剖析
  • 洛谷5318C语言题解
  • Linux CAN 驱动浅析
  • 私有知识库 Coco AI 实战(二):摄入 MongoDB 数据
  • 【问题解决】本机navicat连接云服务器mysql
  • 工作记录9
  • 【Pandas】pandas DataFrame mod
  • 【复盘】cpu飙升引发的连锁反应
  • 国防部:菲挑衅滋事违背地区国家共同利益
  • 习近平举行仪式欢迎肯尼亚总统鲁托访华
  • 韩国检方起诉前总统文在寅
  • 夜读丨一条鱼的使命
  • 去年9月就提出辞任中国乒协主席,刘国梁谈辞职原因
  • 研讨会|中国古代石刻与历史研究的多重图景