【题解】[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;
}
相关推荐
草莓熊Lotso19 分钟前
【C++ 基于微服务的即时通讯系统】etcd + brpc 从安装到服务注册发现全链路实战
网络·数据库·c++·redis·微服务·etcd
HPtiger6 小时前
数据结构——选择排序
数据结构·算法·排序算法
康实训6 小时前
老年评估仿真实训平台架构设计与老年人能力评估实训室教学落地实践
大数据·人工智能·算法·实训室·实训室建设
DongQiShanRen6 小时前
六根硬件映射(四):归属澄清、两处不一致与卷十一总表
开发语言·人工智能·算法·rust·哈希算法·集成学习
CCblandy7 小时前
雪花 ID 四个边界实测:时钟回拨重号 500 个,前端 JSON.parse 直接丢精度
后端·python·算法·性能优化·架构
丑梨数据7 小时前
信创环境里跑文档工具:格式兼容与本地合规的几个坑
数据结构·人工智能·算法·决策树·排序算法
把我格子衫拿来7 小时前
VTK 9.5 与 Qt 6 联合开发:从嵌入三维窗口到可维护的桌面架构
c++·python·qt·vtk·三维可视化
无忧.芙桃7 小时前
C++语言原理与实践(十六):多态机制讲解
开发语言·c++·多态
erzong2319 小时前
Linux操作系统:线程概念与控制
linux·算法
wuminyu9 小时前
JMM学习总结:深入理解happens-before关系
java·linux·c语言·jvm·c++