关于树论【主席树】

很后悔之前在XGC大佬讲的时候没认真听(其实讲的不q不c,幸好了解了一下),现在搞搞差不多理解了。

这个东西是线段树的进化版,强大在于实现了可持久化,后一刻可以参考前一刻的状态。
裸题:给n(1<=n<=100000)个数字a[1],a[2],......,a[n](0<=a[i]<=1000000000),m(1<=m<=100000)次询问l到r之间的第k小的值。
对于这种频繁询问l~r的第k小,线段树原地爆炸。

所以说要用主席树。
首先对于线段树的定义有一点不同(线段树求他的最小值应该是按数列位置排,管理的点有个mn记录区间最小),而在主席树,他的叶子节点是记录这个区间有多少个这个数,举个例子,比如数列里有3个1,那管理1~1的点c值为3,然后回溯更新父亲,所以说,为了防止读入的数值太大以至于爆内存,所以说要离散化。
对于这个序列每一个前缀,都为他建一棵新树,这个数列管理1~i的值,然而不用想就知道,建这么多树不爆才怪,那怎么办?可以发现,管理1~i-1和管理1~i的树只差一个点,而a[i]只会影响到从叶子节点到根一条路径的值,那这两棵树就可以共用不影响的点,达到省空间的目的。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
struct node
{
    int lc,rc,c;
}tr[2100000];int cnt;
int a[110000],b[110000],rt[110000];
int maketree(int x,int l,int r,int p)
{
    if(x==0)x=++cnt;tr[x].c++;//开新点,然后管理的人数++
    if(l==r)return x;
    int mid=(l+r)/2;
    if(p<=mid)tr[x].lc=maketree(tr[x].lc,l,mid,p);//a[i]应该隶属那个子树
    else       tr[x].rc=maketree(tr[x].rc,mid+1,r,p);
    return x;
}
int Merge(int x,int y)
{
    if(x==0||y==0)return x+y;//假如说x没有这个节点,而y有,那就不用开新点,直接连过去,也就是说一个的点有可能多个父亲
    tr[x].c+=tr[y].c;//x的总人数加上y的
    tr[x].lc=Merge(tr[x].lc,tr[y].lc);
    tr[x].rc=Merge(tr[x].rc,tr[y].rc);
    return x;
}
int findans(int x,int y,int l,int r,int k)
{
    if(l==r)return b[l];//l是离散了的值,找回原来的
    int c=tr[tr[x].lc].c-tr[tr[y].lc].c;//得出区间真正有的点数,就是前缀和的应用
    int mid=(l+r)/2;
    if(k<=c)return findans(tr[x].lc,tr[y].lc,l,mid,k);
    else     return findans(tr[x].rc,tr[y].rc,mid+1,r,k-c);
}
int n,m;
int erfen(int k)//二分实际是得出离散值,不然线段树空间爆炸
{
    int l=1,r=n,mid,ans;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(b[mid]<=k)
        {
            l=mid+1;
            ans=mid;
        }
        else r=mid-1;
    }
    return ans;
}
char ss[10];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    cnt=0;memset(rt,0,sizeof(rt));
    for(int i=1;i<=n;i++)
    {
        rt[i]=maketree(rt[i],1,n,erfen(a[i]));//这棵树里就记录了a[i]
        rt[i]=Merge(rt[i],rt[i-1]);//上一棵树记录a[1]~a[i-1],让新的和这个合并一下(上一棵树保留),就得到a[1]~a[i]了
    }
    int x,y,k;
    while(m--)
    {
        scanf("%d%d%d",&x,&y,&k);
        printf("%d\n",findans(rt[y],rt[x-1],1,n,k));//类似求前缀和,主席树就是按前缀建树啊!
    }
    return 0;
}
时间: 2024-11-05 04:31:54

关于树论【主席树】的相关文章

归并树 划分树 可持久化线段树(主席树) 入门题 hdu 2665

如果题目给出1e5的数据范围,,以前只会用n*log(n)的方法去想 今天学了一下两三种n*n*log(n)的数据结构 他们就是大名鼎鼎的 归并树 划分树 主席树,,,, 首先来说两个问题,,区间第k大 ,,,, 这个问题的通用算法是 划分树,, 说白一点就是把快速排序的中间结果存起来, 举个栗子 原数列 4 1 8 2 6 9 5 3 7 sorted 1 2 3 4 5 6 7 8 9 ........................... qs[0] 4 1 8 2 6 9 5 3 7 q

[可持久化线段树(主席树)]

主席树 抛出问题 如题,给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示序列的长度和查询的个数. 第二行包含N个整数,表示这个序列各项的数字. 接下来M行每行包含三个整数l, r, kl,r,k , 表示查询区间[l, r][l,r]内的第k小值. 输出格式: 输出包含k行,每行1个整数,依次表示每一次查询的结果 解决问题 主席树(可持久化线段树)法 于是针对这个问题,新的数据结构诞生了,也就是主席树. 主席树本名

可持续化线段树(主席树)

什么是主席树 可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本.同时充分利用它们之间的共同数据来减少时间和空间消耗. 因此可持久化线段树也叫函数式线段树又叫主席树. 可持久化数据结构 在算法执行的过程中,会发现在更新一个动态集合时,需要维护其过去的版本.这样的集合称为是可持久的. 实现持久集合的一种方法时每当该集合被修改时,就将其整个的复制下来,但是这种方法会降低执行速度并占用过多的空间. 考虑一个持久集合S. 如图所示,对集合的

神奇的树(主席树思想的应用)

主席树这个概念应该不陌生吧!恩?不会, 戳这里. 主席树(函数式线段树)用的是函数思想,一个节点开数组用来保存自己的左右节点,这样节省许多不必要的空间,还可以保存许多历史状态.而这里我们用的是主席树的函数思想来实现. 上题:http://acm.hdu.edu.cn/showproblem.php?pid=5444 题目大意: 给你一个序列,第一个数为二叉树根节点,之后每个数往上加节点,且保证左节点小于根节点,且保证右节点大于根节点.且每个节点最多有2个子节点.然后再查询位置,每往左找输出一个E

洛谷 [P3834] 可持久化线段树(主席树)

主席树可以存储线段树的历史状态,空间消耗很大,一般开45n即可 #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cstdlib> #include <queue> #define lson l, mid #define rson mid+1, r #define ll long long using name

静态可持久化线段树(主席树)

题目背景 这是个非常经典的主席树入门题——静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示序列的长度和查询的个数. 第二行包含N个正整数,表示这个序列各项的数字. 接下来M行每行包含三个整数 l, r, kl,r,k , 表示查询区间 [l, r][l,r] 内的第k小值. 输出格式: 输出包含k行,每行1个正整数,依次表示每一次查

【模板】【数据结构】【树】主席树

技巧一:离散去重 for(int i=1;i<=n;i++) scanf("%d",&a[i] ),b[i]=a[i]; sort(b+1,b+n+1); int nn=unique(b+1,b+n+1)-b-1;//假设有x个数,那么nn指针会停在第x+1个数的位置 ,nn及以后的都是重复的元素for(int i=1;i<=n;i++) id[i]=lower_bound(b+1,b+nn+1,a[i])-b;//离散过后的新value 技巧二:可持久化数据结构

[SCOI2015]情报传递[树剖+主席树]

[SCOI2015]情报传递 题意大概就是 使得在 \(i\) 时刻加入一个情报员帮您传情报 然后询问 \(x,y,c\) 指 \(x\)到\(y\)多少个人有风险-(大于c)的都有风险-每天风险值+1 看起来-不太可做- 每次要整棵树+1复杂度也需要\(log^2\)的树套树吧 但是显然不用啊 查询的时候减掉就可以了- 所以直接树剖上面无脑主席树就可以了啊- #include <bits/stdc++.h> // #define int long long #define rep(a , b

可持久化线段树(主席树)新手向教程

嗯今天来讲讲一个高端玩意,叫可持久化线段树. 新手向,有点耐心是一定可以懂的 知识储备 首先你得知道线段树是什么,不然也不需要学这个东西 线段树 引入 现在呢我们来思考一个问题,如果题目有需要保存线段树更改前的各个历史版本(比如给一个数列的前n项各建一棵线段树),我们应该怎么存? 每个版本存一棵树吗? 不不不太多了会爆空间的QAQ 事实上,如果要存储前我们只需要修改少量点就可以,因为每两个版本间有很多的重复部分. 解析 圈里面是这个区间包含的数字在数列里出现的次数. 比如:现在我们有数列 2 1

BZOJ3295 动态逆序对 树套树, 树状数组套线段树(主席树)

Orz黄学长,蒟蒻在黄学长的带领下,通过阅读黄学长的代码!终于会了这道题! 首先我想先说一下这道题的思路(准确来说是黄学长的). 很明显,树状数组应该不用讲吧!关键是内存怎么开,维护一些什么样的数据? 其实我们通过观察,很快可以发现,你维护被删的数比维护所有的数轻松多了(不管是空间上,还是时间上).所以我们就可以从这方面想!(其实我一开始的思路,因为这道题我已经看过很久了,一直想写,毕竟是白书里面的一道例题嘛!一开始,蒟蒻的我是打算这样的用树状数组套权值线段树,并且是维护所有的数,我发现空间不够