首页 > 其他分享 >【主席树】洛谷 P3834 可持久化线段树 2

【主席树】洛谷 P3834 可持久化线段树 2

时间:2023-08-25 19:01:27浏览次数:38  
标签:rt node begin 洛谷 int 线段 cin P3834 return

【主席树】洛谷 P3834 可持久化线段树2

题目链接:https://www.luogu.com.cn/problem/P3834

主席树是可持久化线段树的一种,也叫做可持久化权值线段树,主要可以用来O(logn)求静态区间的第k小数。

总所周知,普通线段树每次修改会遍历logn个点,那么我们在每次修改时都把这logn个点复制一份出来再修改,生成一个历史版本,就是可持久化线段树了,这里每一个点都是动态开点,而不是提前开好的,所以每个点内要存他的左右儿子节点的编号,不再是传统线段树的 2*p 和 2*p+1。

主席树有两种写法,一个是提前分配好所有空间,一个是使用指针。

需要注意的是,指针的写法时空复杂度一般要比普通写法大一倍,在这道洛谷板题的具体表现就是:普通写法(634ms,40.79MB),指针写法(1.18s,101.34MB)。

所以比赛时还是用普通写法稳妥一点,空间开到 N<<6 就保证够了。

代码(提前分配空间)

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;

constexpr int N = 2e5;
struct node {
    int l, r;
    int sum;
} tr[N << 6];
int cnt;
int add(int p, int l, int r, int x) {
    int u = ++cnt;
    tr[u] = tr[p];
    tr[u].sum++;
    if (l == r) return u;
    int m = (l + r) / 2;
    if (x <= m) {
        tr[u].l = add(tr[u].l, l, m, x);
    } else {
        tr[u].r = add(tr[u].r, m + 1, r, x);
    }
    return u;
}
int query(int p, int q, int l, int r, int k) {
    if (l == r) return l;
    int m = (l + r) / 2;
    int x = tr[tr[q].l].sum - tr[tr[p].l].sum;
    if (x >= k) {
        return query(tr[p].l, tr[q].l, l, m, k);
    } else {
        return query(tr[p].r, tr[q].r, m + 1, r, k - x);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    auto b = a;
    sort(b.begin(), b.end());
    b.erase(unique(b.begin(), b.end()), b.end());
    int tot = b.size();
    auto getid = [&](int x) {
        return lower_bound(b.begin(), b.end(), x) - b.begin();
    };
    vector<int> rt(n + 1);
    for (int i = 0; i < n; i++) {
        rt[i + 1] = add(rt[i], 0, tot - 1, getid(a[i]));
    }

    for (int i = 0; i < m; i++) {
        int l, r, k;
        cin >> l >> r >> k;
        l--, r--;

        int id = query(rt[l], rt[r + 1], 0, tot - 1, k);
        cout << b[id] << '\n';
    }

    return 0;
}

代码(指针)

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;

struct node {
    node *l;
    node *r;
    int sum;
    node() : l{}, r{}, sum{} {}
};
node *add(node *p, int l, int r, int x) {
    node *n = new node();
    if (p) *n = *p;
    n->sum++;
    if (l == r) return n;
    int m = (l + r) / 2;
    if (x <= m) {
        n->l = add(n->l, l, m, x);
    } else {
        n->r = add(n->r, m + 1, r, x);
    }
    return n;
}
int query(node *p, node *q, int l, int r, int k) {
    if (l == r) return l;
    int nq = (q && q->l ? q->l->sum : 0);
    int np = (p && p->l ? p->l->sum : 0);
    int num = nq - np;
    int m = (l + r) / 2;
    if (num >= k) {
        return query(p ? p->l : nullptr, q ? q->l : nullptr, l, m, k);
    } else {
        return query(p ? p->r : nullptr, q ? q->r : nullptr, m + 1, r, k - num);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    auto b = a;
    sort(b.begin(), b.end());
    b.erase(unique(b.begin(), b.end()), b.end());
    int tot = b.size();
    auto getid = [&](int x) {
        return lower_bound(b.begin(), b.end(), x) - b.begin();
    };
    vector<node *> rt(n + 1);
    for (int i = 0; i < n; i++) {
        rt[i + 1] = add(rt[i], 0, tot - 1, getid(a[i]));
    }

    for (int i = 0; i < m; i++) {
        int l, r, k;
        cin >> l >> r >> k;
        l--, r--;

        int id = query(rt[l], rt[r + 1], 0, tot - 1, k);
        cout << b[id] << '\n';
    }

    return 0;
}

标签:rt,node,begin,洛谷,int,线段,cin,P3834,return
From: https://www.cnblogs.com/blockche/p/17657751.html

相关文章

  • 可持久化线段树标记永久化?可刺激化修道士表舅已经黑!
    关于可刺激化修道士表舅已经黑。因为傻逼lxd告诉我我的表舅已经黑写法是错误的,所以稀里糊涂的让他改成了他的那种写法。但是我的也是对的。比如区间加和区间查和,维护一个\(tag\),表示表舅的值。然后在区间加的时候,经过的区间的\(sum\)的值可以直接加,但是只有在if(x<=l&......
  • 洛谷100题计划 (15/100)
    洛谷100题计划(15/100)P1094[NOIP2007普及组]纪念品分组-洛谷|计算机科学教育新生态(luogu.com.cn)要使得分组最少,其实就是要让一个大的和一个小的放一起,如果大的和小的一起放超过了\(w\),那大的就应该单独放,所以排完序之后,我们可以用双指针从两边寻找可以放一起的......
  • 线段树
    线段树vs树状数组代码长度:树状数组段可扩展性:线段树强,二树状数组仅局限于和的处理思维难度:线段树简单比如区查区改树状数组还要打开多项式搞延迟标记:为了处理当修改区间是\([1,n]\)时所有节点都要被修改一遍的情况如果修改区间覆盖当前区间,那么这颗子树之内所有节点......
  • 【230823-3】▲ABC中,∠ABC=90°,AB=4,BC=3,点D在线段AC上,若角BDC=45°,则BD=?,Cos∠ABD=?
    ......
  • 洛谷100题计划(10/100)
    洛谷100题计划(10/100)P1031[NOIP2002提高组]均分纸牌-洛谷|计算机科学教育新生态(luogu.com.cn)因为第\(1\)堆只能移动到第\(2\)堆,且第\(N\)堆只能移动到第\(N-1\)堆,所以直接从左边往右边转移就行,这里是都减了一个平均数,看所有堆都差多少,如果左右两个都没过平均数......
  • 【题解】洛谷 P1002 [NOIP2002 普及组] 过河卒
    原题链接解题思路这是一道经典的动态规划题目。如果尝试使用深度优先搜索(dfs)或广度优先搜索(bfs)做就会获得TLE(注意数据范围)。于是我们想到了更为高级的动态规划(DynamicProgramming,dp)。简略介绍动态规划算法的核心思想:把原问题分解为相对简单的子问题的方式求解复杂问题。......
  • 数学——点到线段的最短距离
    点到线段最短距离的运算与点到直线的最短距离的运算二者之间存在一定的差别,即求点到线段最短距离时需要考虑参考点在沿线段方向的投影点是否在线段上,若在线段上才可采用点到直线距离公式。通俗的说,我们按照求点到直线的距离作垂线后,交点不一定在线段上。如图\(1\)所示。通常......
  • [Trick] [算法学习笔记] 线段树
    事先声明:本文并非线段树教学。只是一些理解Trick。若您需从0学起线段树建议您移步其他博文呢qwq感谢Idea提供尺子姐姐的博客!,尺子好闪,拜谢尺子!我们在学习线段树的时候,对于乘法“lazytag先乘再加”是不是难以理解?这里介绍一种线段树思考方法。我们可以将序列中的每个元素......
  • 线段树进阶
    多信息合并\(\text{GSS3Solution}\)\(\text{link}\)对于线段树的每个结点,记录其区间和(\(sum\)),区间前后缀最大子段和(\(lmax,rmax\))和区间最大子段和(\(vmax\))。合并:\[vmax=\max\{vmax_{lson},vmax_{rson},rmax_{lson}+lmax_{rson}\}\]\[lmax=\max\{lmax_{lson},sum_{lson}+lm......
  • 【学习笔记】优化建图相关(线段树优化,倍增优化)
    优化建图发现并没有人写得很详细的样子,那我也摆烂好惹点击查看目录目录前言线段树优化建图单点连区间区间连区间例题解题:倍增优化建图例题解题:前言众所周知,连边的时间复杂度一般是\(O(1)\),但,当连边的对象是一个连续的树上区间的时候,我们或许有更优的连边方式:优化建图。......