首页 > 其他分享 >[NOI2018] 归程 题解

[NOI2018] 归程 题解

时间:2023-01-02 12:12:24浏览次数:81  
标签:dist int 题解 low heap include NOI2018 节点 归程

题目描述

[NOI2018] 归程

思路

题目说,所有海拔 \(\leq p\) 的边都会有积水,因此所有的边分为了两个集合 \(S_车, S_步\),其中 \(S_车\) 所有的边的海拔都 \(> p\),\(S_步\) 反之。

那么我们要求的 \(v\) 到 \(1\) 的最短路就可以转换为 \(S_车\) 中所有点到 \(1\) 的最小值。

如何快速求 \(S_车\) 呢?可以考虑 \(\text{Kruskal}\) 重构树。

对于一棵重构树上的虚点,它所有子节点都可以在经过不超过虚点点权的边的情况下互相到达。

利用这个性质,把所有的边按降序排序并创建 \(\text{Kruskal}\) 重构树。

这样对于虚点 \(u\) 使得它的点权(海拔) \(u_{al} > p\),那么只要它的子节点中有 \(v\) 那么它的其余子节点都可以通过开车到达,为了使能到达的节点数量尽可能的,在满足 \(u_{al} > p\) 的条件下,还要使它的深度 \(u_{depth}\) 最小,意味着 \(u_{al} > p\) 并且 \(u\) 的父亲 \(pa_{al} \leq p\),这样节点 \(u\) 的子节点组成的集合就代表着 \(S_车\) 了。

如何求得这个 \(u\) 呢?从 \(v\) 一步步往上爬不可取,可以树上倍增解决。

令 \(f[j][i]\) 表示从 \(j\) 节点出发向上跳 \(2^i\) 步后到达的节点,转移方程为:\(f[j][i] = f[f[j][i - 1]][i - 1]\),\(2^{i-1}+2^{i-1} = 2^i\)。

知道 \(S_车\) 的下一步便是求 \(S_车\) 中所有点到 \(1\) 的最小值。

这一步可以在预处理树上倍增数组的时候顺便维护,定义一个数组 \(low[]\) 表示这个节点所有子节点到 \(1\) 距离的最小值,转移方程为:\(low[i] = \min(low[i], low[son])\),其中 \(son\) 表示 \(i\) 的儿子。

至此问题解决,整理思路:

  1. 跑一遍 \(\text{Dijkstra}\) 得到所有点到 \(1\) 的最小距离;
  2. 构建 \(\text{Kruskal}\) 重构树;
  3. \(\text{Dfs}\) 预处理树上倍增数组 \(f\) 和子节点到 \(1\) 距离最小值数组 \(low\);
  4. 二进制枚举找到节点 \(u\),并输出 \(low[u]\),记录 \(lastans\) 并多测清空。

时间复杂度:\(O(T(m\log n+m\log m+n\log n+Q\log n))\)。

代码实现

// Problem: P4768 [NOI2018] 归程
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4768
// Memory Limit: 512 MB
// Time Limit: 4000 ms
// Author: Moyou
// Copyright (c) 2022 Moyou All rights reserved.
// Date: 2023-01-02 00:16:28

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;

const int N = 2e5 + 10, M = 8e5 + 10;

int lastans, n, m, Q, K, mp;       // mp对应题目中S
int pcnt,                          // 虚点数量
    pval[(N << 1)],                // 虚点的值(海拔)
    low[(N << 1)];                 // 虚点的所有子节点中到1的最小距离
int h[(N << 1)], ne[M], e[M], idx; // 链式前向星存储Kruskal重构树
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

vector<PII> g[N]; // vector存储用于跑最短路的图

struct qwq // 一条边
{
    int u, v, d, al;
};

int read() // 快读省略

int fa[(N << 1)];
qwq edge[M];
void init() // 初始工作
{
    lastans = 0;
    n = read(), m = read();
    memset(h, -1, sizeof h);
    memset(low, 0x3f, sizeof low);
    idx = 0;
    pcnt = n;
    for (int i = 1; i <= (n << 1); i++)
        g[i].clear();
    for (int i = 1; i <= m; i++)
    {
        int a = read(), b = read(), c = read(), d = read();
        edge[i] = {a, b, c, d};
        g[a].push_back({b, c});
        g[b].push_back({a, c});
    }
    Q = read(), K = read(), mp = read();
    for (int i = 1; i <= n; i++)
        fa[i] = i;
}

int dist[N];
bool st[N];
void dijkstra(int s)
{
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, s});
    dist[s] = 0;
    while (heap.size())
    {
        auto t = heap.top().y;
        heap.pop();
        if (st[t])
            continue;
        st[t] = 1;
        for (auto i : g[t])
        {
            int j = i.x, w = i.y;
            if (dist[j] > dist[t] + w)
            {
                dist[j] = dist[t] + w;
                heap.push({dist[j], j});
            }
        }
    }
    for (int i = 1; i <= n; i++)
        low[i] = dist[i];
}

int find(int x)
{
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

void kruskal() // 构建 Kruskal 重构树
{
    sort(edge + 1, edge + m + 1, [](qwq a, qwq b) { return a.al > b.al; }); // 海拔降序排序
    for (int i = 1; i <= m; i++)
    {
        int x = find(edge[i].u), y = find(edge[i].v);
        if (x == y)
            continue;
        fa[x] = fa[y] = fa[++pcnt] = pcnt;
        pval[pcnt] = edge[i].al; // 点权为海拔
        add(pcnt, x), add(pcnt, y);
    }
}

int f[(N << 1)][23];
void lcadfs(int p, int pa) // 用于预处理的dfs
{
    f[p][0] = pa; // 2^0 = 1,向上跳一步即为自己的父亲
    for (int i = 1; i <= 20; i++) // 转移树上倍增
        f[p][i] = f[f[p][i - 1]][i - 1];
    for (int i = h[p]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == pa) continue;
        lcadfs(j, p); // 向下遍历子节点
        low[p] = min(low[p], low[j]); // 更新low[]数组(塔尖幻视)
    }
}

int get_ans(int v, int p)
{
    for (int i = 20; i >= 0; i--)
        if (f[v][i] && pval[f[v][i]] > p) // f[v][i] == 0 时代表跳出去了,在 pval[f[v][i]] > p 的情况下没跳出去的情况下尽可能地往上跳 
            v = f[v][i];

    return low[v]; // 输出最终答案
}

void output()
{
    while (Q--)
    {
        int v0 = read(), p0 = read();
        v0 = (v0 + K * lastans - 1) % n + 1;
        p0 = (p0 + K * lastans) % (mp + 1); // 题目要求的在线处理

        printf("%d\n", lastans = get_ans(v0, p0));
    }
}

void work()
{
    init(); // 十年OI一场空,多测不清_____
    dijkstra(1);
    kruskal();
    lcadfs(pcnt, 0);
    output();
}

int main()
{
    int T = read();
    while (T--)
    {
        work();
    }
    return 0;
}

标签:dist,int,题解,low,heap,include,NOI2018,节点,归程
From: https://www.cnblogs.com/MoyouSayuki/p/17019684.html

相关文章

  • Codeforces Round 789 div2 A-E题解 & 处理手法思考
    A.TokitsukazeandAllZeroSequence这题给一个数列,每次操作对于两个不相同的数字可以吧大的变成min,两个相同的话一个变为0问最少操作多少次能将整个数组变为0首......
  • AGC059 题解 (不含 F)
    去年的比赛拖到今年发了呢...AtcoderProvingContestA.MyLastABCProblem(2000)场上打表找规律找了半天考虑如何处理单个询问。显然连续相同字母可以缩起来(即不......
  • SDK更新不了问题解决
    问题阐述相信大家在更新SDK的时候都会遇到更新不了的问题,而且打不开Google搜索,这是因为天朝墙了Google,所以要么只能通过科学上网或者改HOSTS才能访问,更新SDK!本节来介绍两种......
  • CF319D 题解
    题意传送门给你一个字符串\(S\),要求你每次找到一个最短的并且最左边的形如\(XX\)(即由两个相同的字符串拼接而成)的子串,然后把这个字符串从\(XX\)变成\(X\)。问无法操......
  • 牛客小白月赛64 C题 题解
    题目链接题意描述这一题的意思其实就是,让你构造一个\(n*k\)的矩阵,使得第i列的总和为i,同时使得:每一列的任意两个数之间的差不大于1,且任意两行之间的总和差不大于1。......
  • CCNUACM寒假培训第二周周赛部分题解(ACF)
    A题大意:给出n个数,每次可以选择任意一个数进行加一操作,可执行k次,求最大值可能的最大最小值考虑最大值最大,即所有操作都对初始n个数中的最大值进行,答案即max(a1,.....,an)+......
  • [POI2007]GRZ-Ridges and Valleys 题解
    (2022-12-28)AcWing1106洛谷P3456题目大意找出一个图中所有大于(或小于)周围相邻的非连通块点的所有连通块个数。就是说,对于一个连通块:如果它周围的点都低于它,那么山......
  • [USACO22DEC] Cow College B 题解
    洛谷P8897AcWing4821题目描述有\(n\)头奶牛,每头奶牛愿意交的最大学费位\(c_i\),问如何设置学费,可以使赚到的钱最多。\(1\len\le10^5,1\lec_i\le10^6\)做法分析......
  • 武汉工程大学第五届程序设计新生赛 I题 题解
    (2022,12,3)原题链接(来自牛客竞赛)抽象题意题目有点长,我们需要抽象出一个模型:一个长度为\(n\)的序列\(a_i\),从\(a_1\)开始向后跳,每次可以从\(a_i\)跳到下一位\(a_{i+1}\),......
  • 洛谷P4146 序列终结者 题解 splay tree
    题目链接:​​https://www.luogu.com.cn/problem/P4146​​题目大意:支持:区间更新(+x)区间翻转区间查询(最大值)解题思路:几乎和​​AcWing2437.Splay​​这题一模一样。示例程......