P9370 APIO2023 赛博乐园 / cyberland
P9370 APIO2023 赛博乐园 / cyberland。
题目就是让我们求一个有各种优惠政策的单源点
- 到达能力为
的点,可以让之前走过的距离除以 。 - 到达能力为
的点,可以让之前走过的距离直接变成 。
有限制:不能经过
优惠政策最短路,再加上 97 分中
上面加粗的两句正是分层图最短路的两条重要特征。于是想到建立
这里默认读者已经学会分层图,若不会,请先学习并完成分层图模板:JLOI2011 飞行路线。这里还有分层图题单,供读者练习。
原图为第
最短路算法必须使用 dijkstra,用 spfa 虽然可以通过原题的数据,但复杂度是错的,讨论区中有 hack 数据。
定义
思路一
首先想一下,到达能力为
因此,我们将所有能从
那么能力为
考虑在 dijkstra 枚举出边时进行如下松弛:
- 尝试用
更新 。代表不使用优惠政策。 - 尝试用
更新 。代表使用优惠政策。
同时注意
跑一个源点为
然而这种思路的问题在于,上面的第二种松弛并不是平凡的最短路松弛,违背了 dijkstra 的正确性。
但是只要进行一步修改,上面的思路就是正确的了:
考虑朴素的 dijkstra 的优先队列,我们会优先取出
- 对于 不同层 的两个点
和 ,不妨令 ,则 更优先,不管最短路; - 对于 同层 的两个点
和 ,则取 更小的更优先。
每次取最优先的开始松弛就一定没有问题。解释一下原因:
分层图是单向导通的,高层点的最短路一定不会影响低层点。
观察全局上优先队列取出的过程,大体上是优先低层,然后再优先低距离。
也就是说,在第
我们称 dijkstra 的过程中,层号为
那么在第
先不进行第
一般的单源最短路,在初始时,都是源点距离为
而这里的单源最短路,若干点的距离已知,而其它点的距离为
会发现这种变化是不影响 dijkstra 的。可以联想一下,将这些距离已知的点
显然 dijkstra 经过一次松弛之后仍然是正确的,所以该算法是没问题的。
上面我们阐述了从第
因此所有阶段都是正确的,算法正确。
这里设边数为小写
100 分做法
上面的做法是 97 分的,
我们发现,事实上用一些次优惠政策之后,最短路会变的很低,远远低于精度。
具体来说,本题中最短路最大不超过边数乘边权最大值,即
也就意味着使用
而题目要求精度
因此,令
当然,如果你当心上面的
#include <bits/stdc++.h>
const int N = (int)1e5 + 5;
const int K = 75;
typedef std :: pair <int, int> pii;
struct node {
int p;
int u;
double dis;
bool operator < (const node b) const {
if (this -> p != b.p)
return this -> p > b.p;
return this -> dis > b.dis;
}
};
std :: vector <pii> G[N];
double dis[K][N];
std :: bitset <N> vis[K];
std :: bitset <N> con;
inline void init(int n, int k) {
con.reset();
for (int u = 0; u < n; ++u)
G[u].clear();
for (int p = 0; p <= k; ++p)
vis[p].reset();
for (int p = 0; p <= k; ++p)
for (int u = 0; u < n; ++u)
dis[p][u] = 1e17;
}
void dfs(int u, int t) {
con.set(u);
for (pii e : G[u]) {
int v = e.first;
if (!con[v] && v != t)
dfs(v, t);
}
}
double solve(int n, int m, int k, int t, std :: vector <int> us, std :: vector <int> vs, std :: vector <int> wei, std :: vector <int> a) {
if (k > 70)
k = 70;
init(n, k);
for (int i = 0; i < m; ++i) {
int u = us[i], v = vs[i], w = wei[i];
G[u].push_back({v, w});
G[v].push_back({u, w});
}
dfs(0, t);
std :: priority_queue <node> q;
for (int u = 0; u < n; ++u) {
if (con[u] && (a[u] == 0 || u == 0)) {
dis[0][u] = 0;
q.push({0, u, 0});
}
}
while (!q.empty()) {
node now = q.top();
q.pop();
int u = now.u, p = now.p;
if (vis[p][u] || u == t)
continue;
vis[p].set(u);
for (pii e : G[u]) {
int v = e.first, w = e.second;
if (dis[p][v] > dis[p][u] + w) {
dis[p][v] = dis[p][u] + w;
if (!vis[p][v])
q.push({p, v, dis[p][v]});
}
if (a[v] == 2 && p < k) {
if (dis[p + 1][v] > (dis[p][u] + w) / 2) {
dis[p + 1][v] = (dis[p][u] + w) / 2;
if (!vis[p + 1][v])
q.push({p + 1, v, dis[p + 1][v]});
}
}
}
}
double ans = DBL_MAX;
for (int p = 0; p <= k; ++p)
ans = std :: min(ans, dis[p][t]);
if (ans > 1e15)
return -1;
return ans;
}
思路二
如果你想不到修改堆排序方式,还有一种思路,我们只需要对原题进行转化并进行合理地分层之后,套一个裸 dijkstra 就可以解决。
考虑建立反图(虽然无向图的反图和原图是一样的),观察反的最短路径上,优惠政策的含义:
- 原先遇到能力为
的点,会把当前距离设置为 ; - 那么现在遇到能力为
的点,相当于让之后走过的所有边权都变成 。 - 原先遇到能力为
的点,会把当前距离减半; - 那么现在遇到能力为
的点,相当于让之后走过的所有边权都变成原来的一半。
我们发现,直接让第
这样以来,裸的 dijkstra 就可以解决问题。此时汇点是
同时注意一下不能途径
#include <bits/stdc++.h>
const int N = (int)1e5 + 5;
const int K = 75;
typedef std :: pair <int, int> pii;
struct node {
int p;
int u;
double dis;
bool operator < (const node b) const {
return this -> dis > b.dis;
}
};
std :: vector <pii> G[N];
double dis[K][N];
std :: bitset <N> vis[K];
double val[K];
inline void init(int n, int k) {
val[0] = 1;
for (int i = 1; i <= k; ++i)
val[i] = val[i - 1] / 2;
val[k + 1] = 0;
for (int u = 0; u < n; ++u)
G[u].clear();
for (int p = 0; p <= k + 1; ++p)
vis[p].reset();
for (int p = 0; p <= k + 1; ++p)
for (int u = 0; u < n; ++u)
dis[p][u] = 1e17;
}
double solve(int n, int m, int k, int t, std :: vector <int> us, std :: vector <int> vs, std :: vector <int> wei, std :: vector <int> a) {
if (k > 70)
k = 70;
init(n, k);
for (int i = 0; i < m; ++i) {
int u = us[i], v = vs[i], w = wei[i];
G[u].push_back({v, w});
G[v].push_back({u, w});
}
std :: priority_queue <node> q;
dis[0][t] = 0;
q.push({0, t, 0});
while (!q.empty()) {
node now = q.top();
q.pop();
int u = now.u, p = now.p;
if (vis[p][u])
continue;
vis[p].set(u);
for (pii e : G[u]) {
int v = e.first, w = e.second;
if (v == t)
continue;// 这里阻止松弛的方式和思路一不同,原理已经解释
if (a[v] == 0) {
if (dis[k + 1][v] > dis[p][u] + w * val[p]) {
dis[k + 1][v] = dis[p][u] + w * val[p];
if (!vis[k + 1][v])
q.push({k + 1, v, dis[k + 1][v]});
}
continue;
}
if (dis[p][v] > dis[p][u] + w * val[p]) {
dis[p][v] = dis[p][u] + w * val[p];
if (!vis[p][v])
q.push({p, v, dis[p][v]});
}
if (a[v] == 2 && p < k) {
if (dis[p + 1][v] > dis[p][u] + w * val[p]) {
dis[p + 1][v] = dis[p][u] + w * val[p];
if (!vis[p + 1][v])
q.push({p + 1, v, dis[p + 1][v]});
}
}
}
}
double ans = DBL_MAX;
for (int p = 0; p <= k + 1; ++p)
ans = std :: min(ans, dis[p][0]);
if (ans > 1e15)
return -1;
return ans;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】