AcWing341. 洛谷P1073, NOIP2009 最优贸易
题目大意
\(~~~~~~\)一个投机倒把的奸商想要通过城市不太健全的贸易系统坑点钱,任意城市都可以买入或者卖出水晶球,他想尽量在便宜的城市买入,在贵的城市卖出,以此赚取更高的差价,他必须从一号城市开始旅行,到\(n\)号城市结束。请问他最多可以赚多少钱?
解题思路
\(~~~~~~\)这题乍一看貌似是毫无头绪的,但是我们可以通过差价高推出,他应该价格尽量低 买入,价格尽量高的卖出,所以我们可以在从\(1\sim n\) 的路径中选择一个分界点,记作\(k\),这个\(k\)点的含义就是奸商决定在\(1\sim k\)的城市中买入,\(k\sim n\)中的城市卖出,基于我们的买入尽量低,卖出尽量高原则,我们可以得到我们的基本思路:
\(~~~~~~\)枚举所有的\(k\)点,找出\(1\sim k\)中需要的价格最小的点(有路径可以到达的),用\(dmin\)数组记录, 再找出\(k\sim n\)中需要的价格最大的点(存在到\(n\)的路径的),用\(dmax\)数组记录,最后统计答案时找出\(dmax_{i}-dmin_{i}\) 的值最大的点
具体实现
\(~~~~~~\)怎么找出\(1\sim k\)中的最小点呢,难道是枚举每条\(a_{i}\sim k(\forall a_{i}\in [1,k])\)的路中最便宜的点吗?这样未免也太慢了。我们可以采用\(SPFA\)算法,枚举从\(1\)开始的单源最短路(这里最短路的松弛操作需要略做改动),这样对于\(\forall k\in [1,n]\),所有的最短距离都计算好了。
对于\(k\sim n\)的最大点我们也采用类似以上的做法:
\(~~~~~~\)我们枚举从\(k\)开始的最短路,松弛操作同样改动,那我们就可以算出\(\forall k\in [1,n]\)到\(n\)点的最大价值了。但是这样需要做\(n\)遍\(SPFA\),一定是不能再时间上通过的,所以该算法应该经过一点改进。
\(~~~~~~\)经过观察可以发现所有的汇点都是\(n\),我们就可以自然地建一个反图(将所有的边方向反转),这样子反图上跑出来的以\(n\)为源点的最长路,就是原图上各点到\(n\)的最长路了。
对于代码
\(~~~~~~~~~~~~~~~~~~~~~~~~\)我们可以写两个\(SPFA\),一个求最短路,一个求最长路
\(~~~~~~~~~~~~~~~~~~~~~~~~\)也可以合并处理,这里两份代码都放上来了
代码
两个\(SPFA\)
#include <queue>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 3e5 + 10, M = 2e6 + 10, INF = 0x3f3f3f3f;
int dmax[N], dmin[N], st[N];
int h[N], rh[N], e[M], ne[M], w[N], idx;
int n, m;
void add(int *h, int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void spfa()
{
queue<int> q;
memset(st, 0, sizeof st);
memset(dmin, 0x3f, sizeof dmin);
dmin[1] = w[1], st[1] = true, q.push(1);
while (q.size())
{
auto t = q.front(); q.pop();
st[t] = false ;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dmin[j] > min(dmin[t], w[j]))
{
dmin[j] = min(dmin[t], w[j]);
if (!st[j]) st[j] = true, q.push(j);
}
}
}
}
void rspfa()
{
queue<int> q;
memset(st, 0, sizeof st);
memset(dmax, 0xcf, sizeof dmax);
dmax[n] = w[n], st[n] = true, q.push(n);
while (q.size())
{
auto t = q.front(); q.pop();
st[t] = false ;
for (int i = rh[t]; ~i; i = ne[i])
{
int j = e[i];
if (dmax[j] < max(dmax[i], w[j]))
{
dmax[j] = max(dmax[i], w[j]);
if (!st[j]) q.push(j);
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
scanf("%d", &w[i]);
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
while (m -- )
{
int u, v, t;
scanf("%d%d%d", &u, &v, &t);
add(h, u, v), add(rh, v, u);
if (t == 2) add(h, v, u), add(rh, u, v);
}
spfa(), rspfa();
int res = 0;
for (int i = 1; i <= n; i ++ )
res = max(res, dmax[i] - dmin[i]);
printf("%d\n", res);
return 0;
}
观察到我们写的两个\(SPFA\)其实有很多相似点,所以可以合并成一个,加上一些参数就行(用来区分, 例如:遍历的邻接表不同,起点不同,要求的最短和最长性质不同)
但是在合并的\(SPFA\)中要注意:\(memset\)的时候因为我们传进去的距离数组只是一个指针,所以与我们要的字节大小不符合,所以应该写成memset(d, 0x3f, sizeof dmin);当然这只是示例
\(合并的SPFA\)
#include <queue>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 2e5 + 10, M = 1e6 + 10, INF = 0x3f3f3f3f;
int dmax[N], dmin[N], st[N];
int h[N], rh[N], e[M], ne[M], w[N], idx;
int n, m;
void add(int *h, int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void spfa(int *h, int *d, int S, int type)
{
memset(st, 0, sizeof st);
if (type) memset(d, 0xcf, sizeof dmax);
else memset(d, 0x3f, sizeof dmin);
queue<int> q;
d[S] = w[S], q.push(S), st[S] = true;
while (q.size())
{
auto t = q.front(); q.pop();
st[t] = false ;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (type) {
if (d[j] < max(d[t], w[j]))
{
d[j] = max(d[t], w[j]);
if (!st[j]) q.push(j);
}
}
else {
if (d[j] > min(d[t], w[j]))
{
d[j] = min(d[t], w[j]);
if (!st[j]) q.push(j);
}
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
scanf("%d", &w[i]);
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
while (m -- )
{
int u, v, t;
scanf("%d%d%d", &u, &v, &t);
add(h, u, v), add(rh, v, u);
if (t == 2) add(h, v, u), add(rh, u, v);
}
spfa(h, dmin, 1, 0); // 正着跑最短路
spfa(rh, dmax, n, 1); // 反着跑最长路
int res = 0;
for (int i = 1; i <= n; i ++ )
res = max(res, dmax[i] - dmin[i]);
printf("%d\n", res);
return 0;
}
写法对比
两个\(SPFA\):时间\(250ms\),码量\(94\)行,容易\(Debug\)(原因:分开逻辑清晰一点)
一个\(SPFA\):时间\(227ms\),码量\(80\)行,容易出\(Bug\),不好\(Debug\)(原因:我菜)
\(\color{Green}{Accepted!}\)
AcWing341. 洛谷P1073, NOIP2009 最优贸易的更多相关文章
- 【题解】洛谷P1073 [NOIP2009TG] 最优贸易(SPFA+分层图)
次元传送门:洛谷P1073 思路 一开始看题目嗅出了强连通分量的气息 但是嫌长没打 听机房做过的dalao说可以用分层图 从来没用过 就参考题解了解一下 因为每个城市可以走好几次 所以说我们可以在图上 ...
- 洛谷1073 NOIP2009 最优贸易
题目大意 C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市.任意两个城市之间最多只有一条道路直接相连.这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双 ...
- 【洛谷P1073】最优贸易
题目大意:给定一个 N 个点,M 条边(存在反向边)的有向图,点有点权,求一条从 1 到 N 的路径上,任意选出两个点 p,q (p 在前,q在后),两点点权的差值最大. 根据最短路的 dp 思想,可 ...
- 【洛谷 P1073】 最优贸易 (Tarjan缩点+拓扑排序)
题目链接 先\(Tarjan\)缩点,记录每个环内的最大值和最小值. 然后跑拓扑排序,\(Min[u]\)表示到\(u\)的最小值,\(ans[u]\)表示到\(u\)的答案,\(Min\)和\(an ...
- [Luogu 1073] NOIP2009 最优贸易
[Luogu 1073] NOIP2009 最优贸易 分层图,跑最长路. 真不是我恋旧,是我写的 Dijkstra 求不出正确的最长路,我才铤而走险写 SPFA 的- #include <alg ...
- [NOIP2009]最优贸易(图论)
[NOIP2009]最优贸易 题目描述 CC 国有 \(n\) 个大城市和 \(m\) 条道路,每条道路连接这 \(n\) 个城市中的某两个城市.任意两个城市之间最多只有一条道路直接相连.这 \(m\ ...
- 洛谷 P1073 最优贸易 & [NOIP2009提高组](反向最短路)
传送门 解题思路 很长的题,实际上在一个有向图(点有点权)中求一个从起点1到终点n的路径,使得这条路径上点权最大的点与点权最小的点的差值最大(要求必须从点权较小的点能够走到点权较大的点). ——最短路 ...
- 【洛谷P1073】[NOIP2009]最优贸易
最优贸易 题目链接 看题解后感觉分层图好像非常NB巧妙 建三层n个点的图,每层图对应的边相连,权值为0 即从一个城市到另一个城市,不进行交易的收益为0 第一层的点连向第二层对应的点的边权为-w[i], ...
- 洛谷 P1073 最优贸易 解题报告
P1073 最优贸易 题目描述 \(C\)国有\(n\)个大城市和\(m\)条道路,每条道路连接这\(n\)个城市中的某两个城市.任意两个城市之间最多只有一条道路直接相连.这\(m\)条道路中有一部分 ...
- 洛谷P1073 最优贸易 [图论,DP]
题目传送门 最优贸易 题目描述 C 国有n 个大城市和m 条道路,每条道路连接这n 个城市中的某两个城市.任意两个城市之间最多只有一条道路直接相连.这m 条道路中有一部分为单向通行的道路,一部分为双向 ...
随机推荐
- Qemu/Limbo/KVM镜像 Ubuntu 22.04 精简版,可运行Windows软件,内存占用不到200M
镜像特征: Ubuntu 22.04系统 内置Wine 7.8,可运行大量Windows 软件 高度精简,内存占用仅200M不到. 自制UI,Windows3.1风格. 完全开源 镜像说明: 用户名为 ...
- C++实现双向RRT算法
C++实现双向RRT算法 背景介绍 RRT(Rapidly-exploring Random Trees)是Steven M. LaValle和James J. Kuffner Jr.提出的一种通过所 ...
- 分布式存储系统之Ceph集群状态获取及ceph配置文件说明
前文我们了解了Ceph的访问接口的启用相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/16727620.html:今天我们来聊一聊获取ceph集群状态和 ...
- 2022.9.10-2022.9.12 Java第一次课总结
本节课中的问题总结如下: 1.Java的基本运行单位是类还是方法? 答:Java的基本运行单位是类. 2.类由什么组成? 答:类由变量/方法/属性/事件等部分组成,其中方法就是我们所熟悉的函数,属性即 ...
- springboot的注解声明过滤器配置错误导致拦截所有请求。
究其原因, 原来spring 扫包时候 扫了Webfilter 注解,注册了一次过滤匹配路径,扫了Component注解(又注册了一次过滤匹配路径,默认是全路径). Component注解后于WebF ...
- vivo互联网机器学习平台的建设与实践
vivo 互联网产品团队 - Wang xiao 随着广告和内容等推荐场景的扩展,算法模型也在不断演进迭代中.业务的不断增长,模型的训练.产出迫切需要进行平台化管理.vivo互联网机器学习平台主要业务 ...
- 简析 Linux 的 CPU 时间
从 CPU 时间说起... 下面这个是 top 命令的界面,相信大家应该都不陌生. top - 19:01:38 up 91 days, 23:06, 1 user, load average: 0. ...
- 【Java8新特性】- Optional应用
Java8新特性 - Optional应用 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! ...
- 更换SSD系统启动盘-PE重装系统
由于把C盘所在的磁盘替换为了另一张大容量SSD,因此必须借助于PE系统重装Win10,基本步骤如下: 由于每个人电脑不同,遇到的情况也不同,我的电脑是惠普暗影精灵,在PE系统中需要用分区助手把新磁盘进 ...
- 1.WEB应用模式
1. Web应用模式 在开发Web应用中,有两种应用模式: 前后端不分离[客户端看到的内容和所有界面效果都是由服务端提供出来的.] 前后端分离[把前端的界面效果(html,css,js分离到另一个服务 ...