【Coel.解题报告】【没有忧虑的梦境世界】[HNOI2012]永无乡

Never Land ,永远的童年,不朽以及避世。

题前碎语

连写了三篇笔记了,写个解题报告调整一下心情。
其实是因为 Splay 进阶操作没做完

题目简介

P3224 [HNOI2012]永无乡
洛谷传送门

题目描述

永无乡包含 n 座岛,编号从 1n ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 n 座岛排名,名次用 1n 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。如果从岛 a 出发经过若干座(含 0 座)桥可以 到达岛 b ,则称岛 a 和岛 b 是连通的。

现在有两种操作:

B x y 表示在岛 x 与岛 y 之间修建一座新桥。

Q x k 表示询问当前与岛 x 连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪座,请你输出那个岛的编号。

输入输出格式

输入格式

第一行是用空格隔开的两个整数,分别表示岛的个数 n 以及一开始存在的桥数 m

第二行有 n 个整数,第 i 个整数表示编号为 i 的岛屿的排名 pi

接下来 m 行,每行两个整数 u,v,表示一开始存在一座连接编号为 u 的岛屿和编号为 v 的岛屿的桥。

接下来一行有一个整数,表示操作个数 q

接下来 q 行,每行描述一个操作。每行首先有一个字符 op,表示操作类型,然后有两个整数 x,y

  • opQ,则表示询问所有与岛 x 连通的岛中重要度排名第 y 小的岛是哪座,请你输出那个岛的编号。
  • opB,则表示在岛 x 与岛 y 之间修建一座新桥。

输出格式

对于每个询问操作都要依次输出一行一个整数,表示所询问岛屿的编号。如果该岛屿不存在,则输出 1

数据规模与约定

  • 对于 20% 的数据,保证 n103, q103
  • 对于 100% 的数据,保证 1mn105, 1q3×105pi 为一个 1n 的排列,op{Q,B}1u,v,x,yn

解题思路

两件事:查询第 k 小,连接两个集合。
查询第 k 小可以用平衡树解决(这里使用 FHQTreap ),连接集合的话就要用并查集了。
注意在使用并查集连接集合的时候,需要进行启发式合并
启发式合并听起来很高大上,但实际上就是优先把小集合合并给大集合
这样做有什么意义呢?直觉上告诉我们,大集合合并给小集合是比小集合合并给大集合慢的,所以采取启发式合并可以降低时间复杂度,并且保持答案不变。
在路径压缩和启发式合并的加成下,并查集能够保持十分优秀的复杂度,平均情况下为 O(mα(m,n)) ,其中 α(m,n) 是阿克曼函数的反函数,增长速度极其缓慢,可以认为是一个不超过 4 的常数;
而如果没有使用启发式合并,仅使用了路径压缩,极端情况下时间复杂度为 O(mlogn),效果大打折扣。


介绍了这么多,回归正题吧。你还想起有正题
一些细节:

  • 对于每个集合使用一棵平衡树,所以不需要使用root
  • 合并集合的时候跑一次 dfs 处理两个集合的节点,注意参数的传递与否。

代码如下:

#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <iostream>

namespace FastIO {
inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}
inline void write(int x) {
    if (x < 0) {
        x = -x;
        putchar('-');
    }
    static int buf[35];
    int top = 0;
    do {
        buf[top++] = x % 10;
        x /= 10;
    } while (x);
    while (top)
        putchar(buf[--top] + '0');
    puts("");
}
} 

using std::swap;
using namespace FastIO;

const int maxn = 1e5 + 10;

int n, m, q;

int size[maxn], val[maxn], pri[maxn], ch[maxn][2], herb[maxn];
//herb用来存岛屿编号
int f[maxn];

int find(int x) {//路径压缩
    return x == f[x] ? x : f[x] = find(f[x]);
}

void pushup(int x) {
    size[x] = size[ch[x][0]] + size[ch[x][1]] + 1;
}

void split(int id, int k, int& x, int& y) {
    if (id == 0)
        x = y = 0;
    else {
        if (val[id] <= k) {
            x = id;
            split(ch[id][1], k, ch[id][1], y);
            pushup(x);
        } else {
            y = id;
            split(ch[id][0], k, x, ch[id][0]);
            pushup(y);
        }
    }
}

int merge(int x, int y) {
    if (x == 0 || y == 0)
        return x + y;
    if (pri[x] < pri[y]) {
        ch[x][1] = merge(ch[x][1], y);
        pushup(x);
        return x;
    } else {
        ch[y][0] = merge(x, ch[y][0]);
        pushup(y);
        return y;
    }
}

void insert(int& id, int x) {
    int y, z, v = val[x];
    split(id, v, y, z);
    id = merge(merge(y, x), z);
}

void dfs(int x, int& y) {
    if (x == 0)
        return;
    dfs(ch[x][0], y);
    dfs(ch[x][1], y);
    ch[x][0] = ch[x][1] = 0;
    insert(y, x);
}

int Heuristic_Merge(int x, int y) {
    if (size[x] > size[y])
        swap(x, y);//启发式合并的精髓
    dfs(x, y);
    return y;
}

void uni(int u, int v) {
    int x = find(u), y = find(v), z;
    if (x == y)
        return;
    z = Heuristic_Merge(f[u], f[v]);
    f[x] = f[y] = f[z] = z;
}

int Query_kth(int id, int k) {
    while (1) {
        if (k <= size[ch[id][0]])
            id = ch[id][0];
        else if (k == size[ch[id][0]] + 1)
            return id;
        else {
            k -= size[ch[id][0]] + 1;
            id = ch[id][1];
        }
    }
}

int main() {
    srand(3224);
    n = read(), m = read();
    for (int i = 1; i <= n; i++) {
        f[i] = i;
        val[i] = read();
        pri[i] = rand();
        size[i] = 1;
        herb[val[i]] = i;
    }
    for (int i = 1; i <= m; i++) {
        int u = read(), v = read();
        uni(u, v);
    }
    q = read();
    while (q--) {
        char op[5];
        scanf("%s", op + 1);//受cirno教导用字符串输入操作符
        if (op[1] == 'B') {
            int u = read(), v = read();
            uni(u, v);
        } else {
            int x = read(), k = read();
            if (size[find(x)] < k)
                puts("-1");
            else
                write(herb[val[Query_kth(find(x), k)]]);
        }
    }
    return 0;
}

题后闲话

不知道说什么,就放个表情叭(

本文作者:Coel's Blog

本文链接:https://www.cnblogs.com/Coel-Flannette/p/16059224.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   秋泉こあい  阅读(31)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑
  1. 1 アイノマテリアル (feat. 花里みのり&桐谷遥&桃井愛莉&日野森雫&MEIKO) MORE MORE JUMP!
アイノマテリアル (feat. 花里みのり&桐谷遥&桃井愛莉&日野森雫&MEIKO) - MORE MORE JUMP!
00:00 / 00:00
An audio error has occurred.