P9370 APIO2023 赛博乐园 / cyberland - 洛谷 (luogu.com.cn)
好题。
follow the eternal Magnetic flow,
decode the endless Enigma below.
1.分层
有特殊减少贡献的限制次数,试试分层最短路。
- arri=0,意思是这个国家可以让当前总通过时间为 0。
这种点和起点没区别,跑多源最短路就行。- arri=2,表示这个国家拥有让当前总通行时间除以 2 的能力。
这种能力只能使用次,注意到大多数数据
,可以从这里入手。
遇到一个点贡献直接除以 ,肯定是不满足正常最短路的正确性的,相当于图中有负环。
但发现多的能力使用次数的状态 ,无法影响较少的能力使用次数的状态。
也就是,状态们在同一层时,优先选走费用少的。
不在同一层时,优先走层数少的。
好了,现在解决 的情况了。
这个分数的占比非常大,近乎是正解,所以正解肯定差不多。
注意到边数 * 边权 。
而这个数除以到精度 以下,
。
也就是 最大
就够用了。
cpp
#include "cyberland.h"
#include<bits/stdc++.h>
using namespace std;
const int N = 7e5 + 10;
struct node {
int x;
double c;
int k;
};
vector<node> G[N];
bool operator<(node na, node nb) {
if (na.k == nb.k) return na.c > nb.c;
return na.k > nb.k;
}
priority_queue<node> Q;
bool v[N], vis[80][N];
double dis[80][N];
void dfs(int x, int ed) {
v[x] = 1;
for (node no : G[x]) {
int y = no.x;
if (!v[y] && y != ed) {
dfs(y, ed);
}
}
}
void init(int n, int K) {
while (!Q.empty()) Q.pop();
for (int i = 0; i < n; i++) {
v[i] = 0;
G[i].clear();
}
for (int i = 0; i <= K; i++) {
for (int j = 0; j < n; j++) {
dis[i][j] = 1e17;
vis[i][j] = 0;
}
}
}
double solve(int n, int m, int K, int ed, vector<int> x,
vector<int> y, vector<int> c, vector<int> arr) {
K = min(K, 70);
init(n, K);
for (int i = 0; i < m; i++) {
G[x[i]].push_back({y[i], (double)c[i], 0});
G[y[i]].push_back({x[i], (double)c[i], 0});
}
dfs(0, ed);
// 多源初始化
for (int i = 0; i < n; i++) {
if (v[i] && (arr[i] == 0 || i == 0)) {
dis[0][i] = 0;
Q.push({i, 0.0, 0});
}
}
while (!Q.empty()) {
node no = Q.top(); Q.pop();
int u = no.x, k = no.k;
if (vis[k][u] || u == ed) continue;
vis[k][u] = 1;
for (node e : G[u]) {
int v = e.x;
double w = e.c;
if (dis[k][v] > dis[k][u] + w) {
dis[k][v] = dis[k][u] + w;
if (!vis[k][v]) {
Q.push({v, dis[k][v], k});
}
}
if (arr[v] == 2 && k < K) {
if (dis[k + 1][v] > (dis[k][u] + w) / 2.0) {
dis[k + 1][v] = (dis[k][u] + w) / 2.0;
if (!vis[k + 1][v]) {
Q.push({v, dis[k + 1][v], k + 1});
}
}
}
}
}
double ans = DBL_MAX;
for (int i = 0; i <= K; i++) {
ans = min(ans, dis[i][ed]);
}
if (ans > 1e15) return -1;
return ans;
}
2.倒推
一个比较新颖的思路。
最短路的本质是贡献必须不断递增,不一定要严格递增。
而这道题的倒着走就符合,遇到 arri=2 的点就到下一层,而第 k 层边的贡献就要除以 。
arri=0 就到 K + 1 层,全部边的费用都是 0。
总的来说,很有意思的一道多层最短路复合题,做完感觉对这个算法的了解更深。
cpp
#include "cyberland.h"
#include<bits/stdc++.h>
using namespace std;
const int N = 7e5 + 10;
struct node {
int x;
double c;
int k;
};
vector<node> G[N];
// 反向思路用裸 Dijkstra,只按距离排序
bool operator<(node na, node nb) {
return na.c > nb.c;
}
priority_queue<node> Q;
bool vis[80][N];
double dis[80][N];
double num[80];
void init(int n, int K) {
// 预计算边权倍数
num[0] = 1.0;
for (int i = 1; i <= K; i++) {
num[i] = num[i - 1] / 2;
}
num[K + 1] = 0; // 手动更改一下第 K + 1 层的权重
while (!Q.empty()) Q.pop();
for (int i = 0; i < n; i++) {
G[i].clear();
}
for (int i = 0; i <= K + 1; i++) {
for (int j = 0; j < n; j++) {
dis[i][j] = 1e17;
vis[i][j] = 0;
}
}
}
double solve(int n, int m, int K, int ed, vector<int> x,
vector<int> y, vector<int> c, vector<int> arr) {
K = min(K, 70);
init(n, K);
for (int i = 0; i < m; i++) {
G[x[i]].push_back({y[i], (double)c[i], 0});
G[y[i]].push_back({x[i], (double)c[i], 0});
}
// 从 ed 出发,目标到 0
dis[0][ed] = 0;
Q.push({ed, 0.0, 0});
while (!Q.empty()) {
node no = Q.top(); Q.pop();
int u = no.x, k = no.k;
if (vis[k][u]) continue;
vis[k][u] = 1;
for (node e : G[u]) {
int v = e.x;
if (v == ed) continue; // 不能走回来
double w = e.c;
if (arr[v] == 0) {
if (dis[K + 1][v] > dis[k][u] + w * num[k]) {
dis[K + 1][v] = dis[k][u] + w * num[k];
if (!vis[K + 1][v]) {
Q.push({v, dis[K + 1][v], K + 1});
}
}
continue; // 清零后不再尝试其他转移
}
if (dis[k][v] > dis[k][u] + w * num[k]) {
dis[k][v] = dis[k][u] + w * num[k];
if (!vis[k][v]) {
Q.push({v, dis[k][v], k});
}
}
if (arr[v] == 2 && k < K) {
if (dis[k + 1][v] > dis[k][u] + w * num[k]) {
dis[k + 1][v] = dis[k][u] + w * num[k];
if (!vis[k + 1][v]) {
Q.push({v, dis[k + 1][v], k + 1});
}
}
}
}
}
// 答案取所有层到达 0 的最小值
double ans = DBL_MAX;
for (int i = 0; i <= K + 1; i++) {
ans = min(ans, dis[i][0]);
}
if (ans > 1e15) return -1;
return ans;
}