【题解】[APIO2023] 赛博乐园 / cyberland

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;
}
相关推荐
洛阳纸贵2 小时前
MATLAB-matlab基础知识
学习·算法·matlab
落羽的落羽2 小时前
【AI】快速理解AI应用的相关名词概念
linux·c++·人工智能·python·计算机网络·算法
刚入门的大一新生2 小时前
Linux-进程控制
linux·运维·服务器·c++
Nil2082 小时前
leetcode 17电话号码的字母组合
算法·leetcode·职场和发展
刃神太酷啦2 小时前
Redis 进阶核心:持久化 (RDB/AOF)、事务与主从复制全解析----《Hello Redis!》(5)
linux·c语言·数据库·c++·redis·缓存·bootstrap
203号居民4 小时前
LeetCode hot 100 — 25. K 个一组翻转链表
算法·leetcode·链表
ocean21034 小时前
2025-2026年AI算法与模型研发面试高频知识点洞察
人工智能·算法·面试
Nil2085 小时前
leetcode 78子集
数据结构·算法·leetcode
喜欢吃燃面5 小时前
深入 C++ STL:从 unordered_set与unordered_map到底层哈希表的原理与实现
数据结构·c++·散列表