「Luogu P3302」[SDOI2013]森林

给出一片森林,每个点有一个权值,要求支持动态连边,并回答任意两点间第 k 小权值,强制在线。\((1\le N,M,T \le 8\times 10^4)\)

Luogu

分析

求第 k 小权值,这个肯定是用主席树了,但连边该怎么办?LCT?可我不会。

我们可以用启发式合并的方法,连边也就是合并两棵树,我们每次将较小的树连到较大的树上去,更新信息就暴力 dfs 较小树中的每一个点就好了。

代码

#include <bits/stdc++.h>

#define N 80003
#define DEBUG puts("ok")
#define File(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout)

using namespace std;

int gi() {
    int x = 0, f = 1; char c = getchar();
    for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
    for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
    return x * f;
}

int n, m, q, len, tot, num;
int val[N], hs[N];
int fa[N][16], dep[N], sz[N], rt[N];
int to[N << 4], nxt[N << 4], hd[N << 4], ecnt;
int tr[N << 1], L[N << 5], R[N << 5], cnt[N << 5];
bool vis[N];

int get(int k) { return lower_bound(hs + 1, hs + 1 + len, k) - hs; }

void insert(int u, int v) { to[++ecnt] = v, nxt[ecnt] = hd[u], hd[u] = ecnt; }

void modify(int lst, int &now, int l, int r, int k) {
    if (!now) now = ++tot;
    cnt[now] = cnt[lst] + 1;
    if (l == r) return;
    int mid = l + r >> 1;
    if (k <= mid) R[now] = R[lst], modify(L[lst], L[now], l, mid, k);
    else L[now] = L[lst], modify(R[lst], R[now], mid + 1, r, k);
}

void dfs(int u, int f, int root) {
    vis[u] = 1, fa[u][0] = f, dep[u] = dep[f] + 1, sz[root]++, rt[u] = root;
    modify(tr[f], tr[u], 1, len, get(val[u]));
    for (int i = 1; i <= 15; ++i) fa[u][i] = fa[fa[u][i - 1]][i - 1];
    for (int i = hd[u]; i; i = nxt[i]) {
        int v = to[i];
        if (v == f) continue;
        dfs(v, u, root);
    }
}

int LCA(int u, int v) {
    if (dep[u] < dep[v]) swap(u, v);
    for (int i = 15; i >= 0; --i)
        if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
    if (u == v) return u;
    for (int i = 15; i >= 0; --i)
        if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}

int query(int u, int v, int w, int f, int l, int r, int k) {
    if (l == r) return l;
    int sum, mid = l + r >> 1;
    sum = cnt[L[u]] + cnt[L[v]] - cnt[L[w]] - cnt[L[f]];
    if (k <= sum) return query(L[u], L[v], L[w], L[f], l, mid, k);
    else return query(R[u], R[v], R[w], R[f], mid + 1, r, k - sum);
}

void merge(int u, int v) {
    insert(u, v), insert(v, u);
    int r1 = rt[u], r2 = rt[v];
    if (sz[r1] > sz[r2]) dfs(v, u, r1);
    else dfs(u, v, r2);
}

int main() {
    int T = gi();
    int u, v, k, lst = 0; char ch[2];
    n = gi(), m = gi(), q = gi();
    for (int i = 1; i <= n; ++i) val[i] = hs[i] = gi();
    sort(hs + 1, hs + 1 + n);
    len = unique(hs + 1, hs + 1 + n) - hs - 1;
    for (int i = 1; i <= m; ++i) {
        u = gi(), v = gi();
        insert(u, v), insert(v, u);
    }
    for (int i = 1; i <= n; ++i) if (!vis[i]) dfs(i, 0, i);
    for (int i = 1; i <= q; ++i) {
        scanf("%s", ch);
        u = gi() ^ lst, v = gi() ^ lst;
        if (ch[0] == 'Q') {
            k = gi() ^ lst;
            int w = LCA(u, v), f = fa[w][0];
            printf("%d\n", lst = hs[query(tr[u], tr[v], tr[w], tr[f], 1, len, k)]);
        }
        else merge(u, v);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/hlw1/p/12266181.html

时间: 2024-08-29 07:42:33

「Luogu P3302」[SDOI2013]森林的相关文章

【主席树启发式合并】【P3302】[SDOI2013]森林

Description 给定一个 \(n\) 个节点的森林,有 \(Q\) 次操作,每次要么将森林中某两点联通,保证操作后还是个森林,要么查询两点间权值第 \(k\) 小,保证两点联通.强制在线. Limitation \(1~\leq~n,~Q~\leq~80000\) Solution 考虑有连边还有查询链上第 \(k\) 大,于是要么用 LCT,要么用主席树. 考虑如果用 LCT 的话,并不能快速的维护两点间链的信息(其实感觉在access的时候乱搞一下有希望在多一个 \(\log\) 的

「Luogu 1821」[USACO07FEB]银牛派对Silver Cow Party

更好的阅读体验 Portal Portal1: Luogu Portal2: POJ Description One cow from each of N farms \((1 \le N \le 1000)\) conveniently numbered \(1 \cdots N\) is going to attend the big cow party to be held at farm #X \((1 \le X \le N)\). A total of \(M (1 \le M \l

「Luogu 2367」语文成绩

更好的阅读体验 Portal Portal1: Luogu Description 语文老师总是写错成绩,所以当她修改成绩的时候,总是累得不行.她总是要一遍遍地给某些同学增加分数,又要注意最低分是多少.你能帮帮她吗? Input 第一行有两个整数\(n\),\(p\),代表学生数与增加分数的次数. 第二行有\(n\)个数,\(a_1 \sim a_n\),代表各个学生的初始成绩. 接下来\(p\)行,每行有三个数,\(x\),\(y\),\(z\),代表给第\(x\)个到第\(y\)个学生每人增

「Luogu P2617」Dynamic Rankings

给出一段序列,每次修改某个数的值和询问区间第 k 小.\((1\le n,m\le 10^5,0\le a_i\le10^9)\) Luogu 分析 动态主席树裸题. 树状数组套主席树,树状数组的每个结点相当于一棵主席树,每次修改操作只在对应树状数组的 logn 个结点所对应的主席树上修改,查询时,将 l - 1 和 r 分别对应的 logn 棵主席树作差即可. 时间复杂度: \(O(n\log^2{n})\) 空间复杂度: \(O(n\log{n})\) 代码 #include <bits/s

「Luogu P3178」[HAOI2015]树上操作

有一棵点数为 \(N\) 的树,以点 \(1\) 为根,且树点有边权.然后有 \(M\) 个操作,分为三种: 操作 1 :把某个节点 \(x\) 的点权增加 \(a\) . 操作 2 :把某个节点 \(x\) 为根的子树中所有点的点权都增加 \(a\) . 操作 3 :询问某个节点 \(x\) 到根的路径中所有点的点权和. Luogu 分析 我们把树上问题利用 \(dfs\) 序转化成序列问题然后直接上线段树解决即可. 考虑将线段树的每个叶子结点设为在原树上的点到根的点权和.对于单点修改,当前结

「Luogu P6101」[EER2]出言不逊

Portal Portal1: Luogu Solution 模拟,先找到在读入字符串内出现次数最多的字符,记录个数,然后以 \(2\) 为指数在现有长度上递增,就可以算出答案. 但是long long会溢出,所以要判断一下,如mx + mx < mx说明已经溢出了,然后就退出答案做个标记,输出的时候\(+1\),否则会死循环,至于__int128,我没试过. 代码纯属是为过而过,没什么可看的. Code #include<bits/stdc++.h> #pragma GCC optim

「 Luogu P2285 」打鼹鼠

解题思路 第一眼看上去觉得要设计一个三维的 DP,$dp[i][j][k]$ 表示在 $(i,j)$ 这个位置上 $k$ 时刻能够打死的最多的鼹鼠. 但是被数据范围卡死.完全开不开数组啊. 然后注意到题目中有句话说保证是按照时间递增序输入的. 想一下,某个鼹鼠能够被打死,是因为他之前被打死的鼹鼠走过来的. 所以只需要考虑每个鼹鼠之前的若干鼹鼠就可.只要两个鼹鼠的距离小于它们出现的时刻只差,那就可以扩展的到. 附上代码 #include <iostream> #include <cstri

「 COGS 2240 」 X 「 Luogu P2885 」 架设电话线

解题思路 首先很容易就想到了一个二维的朴素的 $dp$. 设 $dp[i][j]$ 表示第 $i$ 个位置的电话线杆的高度为 $j$ 时的最小花费,就需要枚举第 $i$ 个电话线杆.第 $i$ 个电话线杆的高度 $j$.第 $i-1$ 个电话线杆的高度 $k$. 状态转移方程如下 $$dp[i][j] = \min \{dp[i-1][k]+|j-k|\times c + (j-h[i])^2\}$$ 但是这样的 $dp$ 过不了这题的数据范围.这个 $dp$ 的时间复杂度是 $\text{O}

「 Luogu P2657 」 windy数

# 题目大意 给出区间 $[a,b]$,求出区间中有多少数满足下列两个条件 不含有前导 $0$. 相邻两个数字之差的绝对值至少是 $2$. # 解题思路 数位 $DP$,用记忆化搜索来实现.设 $dp[i][j]$ 表示现在已经枚举到第 $i$ 位,第 $i+1$ 位是 $j$ 时一共有多少满足条件的数. 还是直接看代码里的注释吧. # 放上代码 #include <algorithm> #include <iostream> #include <cstring> #i