可持久化线段树(待补充)

可持久化线段树初步理解(单点修改):

  当需要同时保留修改前和修改后数据时,可能就要用到可持久化数据结构。考虑线段树的单点修改,实际上只改了一部分节点的值(logn级别),如果重建一棵树,需要nlogn级别的时间和空间。此时,我们可以使用可持久化线段树,将需要被修改的节点建成新的点,并且每次修改给予线段树一个新的根,这样每次修改并且保存历史记录只用logn级别的时间,多开logn级别的空间。

我的实现:线段树使用结构体Node{

  int lson,int rson;

  int val;

};

  代码与线段树差别不大:

1.maintain和build可以无变化

2.update,每次新建节点,从原节点拷贝信息,更新之后返回新建节点的编号

int update(int now,int l,int r,int pos,int val)
{
    int k=tot++;
    tr[k]=tr[now];
    if (l==r)
    {
        tr[k].val+=val;
        return k;
    }
    int mid=(l+r)>>1;
    if (pos<=mid) tr[k].lson=update(tr[now].lson,l,mid,pos,val);
        else tr[k].rson=update(tr[now].rson,mid+1,r,pos,val);
    maintain(k);
    return k;
}

3.query,可以无变化

4.注意调用update和query的时候,选取不同的根节点就能走到不同的历史版本,而且树的结构都是一模一样的

模板题:求[l,r]区间第k小数,n<=100000,询问<=100000

题解:此处直接讨论离散化之后的做法(离散化后n个数为1~n)

  1、先考虑[1,n]区间的第k小数的求法,建一棵线段树,对第i个数a[i],在线段树第a[i]个位置插入1,线段树维护区间中的数的个数,找第k小数的时候直接根据线段树左半边的数的个数就可以决定往左走还是往右走

  2、考虑[l,r]区间的第k小数的求法,建(n+1)棵线段树,其中第i棵已经插入了a[1~i],找第k小数的时候同步沿着l-1和r树走,做下减法之后就得到[l,r]区间的信息,决定往左走还是往右走,当然这里用可持久化线段树,而非真的建(n+1)棵树

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;++i)
const int maxn=100010;
struct Node{
    int lson,rson;
    int val;
}tr[maxn*20];

struct PX{
    int index,val;

    operator <(const PX &b){
        return val<b.val;
    }
}px[maxn];

int tot,root[maxn],num;

void init()
{
    tot=num=0;
}

inline void maintain(int now)
{
    tr[now].val=tr[tr[now].lson].val+tr[tr[now].rson].val;
}

int build(int l,int r)
{
    int k=tot++;        //begin from 0
    if (l==r)
    {
        tr[k].val=0;
        return k;
    }
    int mid=(l+r)>>1;
    tr[k].lson=build(l,mid);
    tr[k].rson=build(mid+1,r);
    maintain(k);
    return k;
}

int update(int now,int l,int r,int pos,int val)
{
    int k=tot++;
    tr[k]=tr[now];
    if (l==r)
    {
        tr[k].val+=val;
        return k;
    }
    int mid=(l+r)>>1;
    if (pos<=mid) tr[k].lson=update(tr[now].lson,l,mid,pos,val);
        else tr[k].rson=update(tr[now].rson,mid+1,r,pos,val);
    maintain(k);
    return k;
}

int query(int lnow,int rnow,int l,int r,int kth)
{
    if (l==r) return l;
    int mid=(l+r)>>1;
    int tp=tr[tr[rnow].lson].val-tr[tr[lnow].lson].val;
    if (kth<=tp) return query(tr[lnow].lson,tr[rnow].lson,l,mid,kth);
        else return query(tr[lnow].rson,tr[rnow].rson,mid+1,r,kth-tp);
}

int n,m,T,k,l,r;
int s[maxn];
int A[maxn],ans[maxn],fans[maxn];

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%d%d",&n,&m);
        rep(i,1,n) scanf("%d",s+i),px[i].index=i,px[i].val=s[i];
        sort(px+1,px+n+1);
        A[++num]=1;ans[px[1].index]=num;fans[num]=s[px[1].index];
        rep(i,1,n) if (px[i].val==px[i-1].val) ++A[num],ans[px[i].index]=num;
                        else {A[++num]=1;ans[px[i].index]=num;fans[num]=s[px[i].index];}
        root[0]=build(1,num);
        rep(i,1,n) root[i]=update(root[i-1],1,num,ans[i],1);
        rep(i,1,m)
        {
            scanf("%d%d%d",&l,&r,&k);
            printf("%d\n",fans[query(root[l-1],root[r],1,num,k)]);
        }
    }
    return 0;
}
时间: 2024-10-15 05:03:59

可持久化线段树(待补充)的相关文章

【BZOJ-4408】神秘数 可持久化线段树

4408: [Fjoi 2016]神秘数 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 475  Solved: 287[Submit][Status][Discuss] Description 一个可重复数字集合S的神秘数定义为最小的不能被S的子集的和表示的正整数.例如S={1,1,1,4,13}, 1 = 1 2 = 1+1 3 = 1+1+1 4 = 4 5 = 4+1 6 = 4+1+1 7 = 4+1+1+1 8无法表示为集合S的子集的

【BZOJ 3674】可持久化并查集加强版&amp;【BZOJ 3673】可持久化并查集 by zky 用可持久化线段树破之

最后还是去掉异或顺手A了3673,,, 并查集其实就是fa数组,我们只需要维护这个fa数组,用可持久化线段树就行啦 1:判断是否属于同一集合,我加了路径压缩. 2:直接把跟的值指向root[k]的值破之. 3:输出判断即可. 难者不会,会者不难,1h前我还在膜这道题,现在吗hhh就当支持下zky学长出的题了. 3673: #include<cstdio> #include<cstring> #include<algorithm> #define read(x) x=ge

poj2104 求区间第k大 可持久化线段树

poj2104 求区间第k大  可持久化线段树 #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef

Codeforces 484E. Sign on Fence 可持久化线段树

大概题意: 给一数组a,问在某一区间L~R中,问对于连续的长为W的一段中最小的数字的最大值是多少. 显然可以转化成二分高度然后判断可行性的问题. 考虑到高度肯定为数组中的某一个值,将数组从大到小排序. 建n棵线段树,对于第 i 棵线段树,将 大于等于a[i] 的叶子的值设置为1,其他的叶子设置为0,问题就转化成了用线段树求某一区间中最长的连续的1的个数,这是一个线段树的经典问题,可以通过维护每个节点的 左边连续和,右边连续和,连续和的最大值 得到. 由于空间问题,不可能建立10^5棵线段树,考虑

【bzoj3956】Count 单调栈+可持久化线段树

题目描述 输入 输出 样例输入 3 2 0 2 1 2 1 1 1 3 样例输出 0 3 题解 单调栈+可持久化线段树 本题是 bzoj4826 的弱化版(我为什么做题总喜欢先挑难的做QAQ) $k$对点对$(i,j)$有贡献,当且仅当$a_k=max(a_{i+1},a_{i+2},...,a_{r-1})$,且$a_k<a_i\&\&a_k<a_j$. 那么我们可以使用单调栈求出i左面第一个比它大的位置$lp[i]$,和右面第一个比它大的位置$rp[i]$,那么点对$(lp

POJ-2104-K-th Number(可持久化线段树)

K-th Number Time Limit: 20000MS   Memory Limit: 65536K Total Submissions: 55456   Accepted: 19068 Case Time Limit: 2000MS Description You are working for Macrohard company in data structures department. After failing your previous task about key inse

COGS 2554. [福利]可持久化线段树

2554. [福利]可持久化线段树 ★★☆   输入文件:longterm_segtree.in   输出文件:longterm_segtree.out   简单对比时间限制:3 s   内存限制:256 MB [题目描述] 为什么说本题是福利呢?因为这是一道非常直白的可持久化线段树的练习题,目的并不是虐人,而是指导你入门可持久化数据结构. 线段树有个非常经典的应用是处理RMQ问题,即区间最大/最小值询问问题.现在我们把这个问题可持久化一下: Q k l r 查询数列在第k个版本时,区间[l,

HDU 5820 (可持久化线段树)

Problem Lights (HDU 5820) 题目大意 在一个大小为50000*50000的矩形中,有n个路灯.(n<=500000) 询问是否每一对路灯之间存在一条道路,使得长度为|x1 – x2| + |y1 – y2|且每个拐弯点都是路灯. 解题分析 官方题解: 除了从左往右扫描一遍外,个人认为还需从右往左扫描一遍,记录右上方的点的信息. 实际实现时,只需将整个图左右对称翻转一下即可. 学习了一下可持久化线段树的正确姿势. 可持久化线段树的空间一定要开大,开大,开大. 下图为模拟的小

【BZOJ3207】花神的嘲讽计划I 可持久化线段树/莫队

看到题目就可以想到hash 然后很自然的联想到可持久化权值线段树 WA:base取了偶数 这道题还可以用莫队做,比线段树快一些 可持久化线段树: 1 #include<bits/stdc++.h> 2 #define ll long long 3 #define uint unsigned int 4 #define ull unsigned long long 5 #define inf 4294967295 6 #define N 100005 7 #define M 100005 8 #