• poj2104 k-th number 主席树入门讲解


    poj2104 k-th number 主席树入门讲解

    定义:主席树是一种可持久化的线段树 又叫函数式线段树

     

    刚开始学是不是觉得很蒙逼啊 其实我也是 

    主席树说简单了 就是

    保留你每一步操作完成之后的线段树 然后有可加减性

    也就是说你每添加的一个点的那棵树都给你保留下来了

    呃 。。。 这么说好像还是有点生涩

    那么就拿poj2104来举例子吧 慢慢讲我觉得会很好的

    题意就是给你一个100000长度的数字 然后100000次询问[L,R]之间第k大的数字是多少

    这个很容易看出来 暴力根本不可以 黑你分分钟的事情啊 

    我们怎么办呢 想想线段树能不能做 想来想去 一颗线段树好像不能这么做 GG

    那么我们做一个美好的假设:

    我们建立100000棵美丽的线段树 每一个线段树的节点 表示这一个区间内有多少个数字

    第一棵线段树保存着把第一个数字插入进去之后 每个区间有多少个数字

    第二棵线段树保存着把第一个 第二个数字插入进去之后 每个区间有多少个数字

    第n棵线段树保存着把第1,2,3。。。。n个数字插入进去之后 每个区间有多少个数字

    好了 我们已经建立了这么多的线段树 我们接下来该怎么办呢?

    对 就是查询 

    可是如何查询呢? 假设我们要查询[l,r]内的第k大

    我们可以拿出第l-1 ,r 棵线段树,然后两者相减(两棵树的更节点相减) 我们想一下 这样不就得到了相当于插入了第l到r个点所建立的一棵线段树 这棵线段树每个节点保留的信息是:这个区间内数字的个数 然后我们往下二分查找 就可以得到第k大了

    比如说r对应的那棵树1-15里面有10个,l-1对应的那棵树1-15有6,那么r-(l-1)的1-15有10-6=4个,如果我现在k=8,那么说明1-15满足不了我,比如我需要到6-20中去找,r那棵树。

    所谓主席树呢,就是对原来的数列[1..n]的每一个前缀[1..i](1≤i≤n)建立一棵线段树,线段树的每一个节点存某个前缀[1..i]中属于区间[L..R]的数一共有多少个(比如根节点是[1..n],一共i个数,sum[root] = i;根节点的左儿子是[1..(L+R)/2],若不大于(L+R)/2的数有x个,那么sum[root.left] = x)。若要查找[i..j]中第k大数时,设某结点x,那么x.sum[j] - x.sum[i - 1]就是[i..j]中在结点x内的数字总数。而对每一个前缀都建一棵树,会MLE,观察到每个[1..i]和[1..i-1]只有一条路是不一样的,那么其他的结点只要用回前一棵树的结点即可,时空复杂度为O(nlogn)。

    现在的问题时 这么庞大的空间开销我们耗费不起 我们该如何建立这样的线段树呢?

    答案就是 我们要尽量利用重复节点 

    我们可以想一下 我们每次建立线段树 相对于前一棵线段树 我们只修改了它的一条路径 最多只有logn的变化 那么我们就存下这logn的变化 尽可能的利用重复节点 就可以达到重复使用的目的 有张图你们自己体会一下 我也是盗图 侵删~

    每次只修改一条路径

    这样就能完成我们的主席树了 

    接下来是我自己写的该题代码 

    [cpp] view plain copy
     
    1. #include <iostream>  
    2. #include <algorithm>  
    3. #include <stdio.h>  
    4. #include <string.h>  
    5. #include <stdlib.h>  
    6. using namespace std;  
    7. #define maxn (int)(1e6+10)  
    8. struct node{  
    9.     int cnt,l,r;  
    10. }treenode[30*maxn];//定义一个结构体吧 要不心累 l 和 r 表示 左右两个节点的序号 这个不是单纯的单个线段树了 这个还是有必要的 最好开20-40倍  
    11. int tree_cnt[maxn];//每个线段树跟节点的坐标 这个是搜索的起点啊  
    12. int init[maxn];//  
    13. int cop[maxn];  
    14. int n,t_cnt=0,newn;//t_cnt是现在数组开到多大了 然后建立下一个的时候注意++t_cnt;  
    15. int getid(int x) {return (int)(lower_bound(init,init+newn,x)-init);}//数据太大 需要离散化  
    16. int insert(int num,int becopyed,int l,int r)//记住返回自己的坐标  
    17. {  
    18.     ++t_cnt;  
    19.     treenode[t_cnt].cnt=treenode[becopyed].cnt+1;  
    20.     int save=t_cnt;  
    21.     int mid=(l+r)/2;  
    22.     if(l==r)  
    23.     {  
    24.         return save;  
    25.     }  
    26.     else if(num<=mid)  
    27.     {  
    28.         treenode[save].l=insert(num,treenode[becopyed].l,l,mid);  
    29.         treenode[save].r=treenode[becopyed].r;  
    30.     }  
    31.     else  
    32.     {  
    33.         treenode[save].r=insert(num,treenode[becopyed].r,mid+1,r);  
    34.         treenode[save].l=treenode[becopyed].l;  
    35.     }  
    36.     return save;  
    37. }  
    38. int query(int x,int y,int k,int l,int r)  
    39. {  
    40.     if(l==r) return l;  
    41.     int p=(treenode[treenode[y].l].cnt-treenode[treenode[x].l].cnt);  
    42.     int mid=(l+r)/2;  
    43.     if(k<=p)  
    44.     {  
    45.         return query(treenode[x].l,treenode[y].l,k,l,mid);  
    46.     }  
    47.     else return query(treenode[x].r,treenode[y].r,k-p,mid+1,r);  
    48. }//一边做减法 一边查询  
    49. void print(int x,int l,int r)  
    50. {  
    51.     cout<<treenode[x].cnt<<' ';  
    52.     if(l==r) {return;}  
    53.     int mid=(l+r)>>1;  
    54.     print(treenode[x].l,l,mid);  
    55.     print(treenode[x].r,mid+1,r);  
    56. }  
    57. int main()  
    58. {  
    59.     int n,qnum;  
    60.     cin>>n>>qnum;  
    61.     for(int i=0;i<n;++i) {scanf("%d",init+i);cop[i]=init[i];}  
    62.     sort(init,init+n);  
    63.     newn=unique(init,init+n)-init;  
    64.     for(int i=1;i<=n;++i)  
    65.     {  
    66.         int p=insert(getid(cop[i-1]),tree_cnt[i-1],0,n);  
    67.         tree_cnt[i]=p;  
    68.         //cout<<p<<endl;  
    69.     }  
    70.     for(int i=0;i<qnum;++i)  
    71.     {  
    72.         int x,y,k;  
    73.         scanf("%d %d %d",&x,&y,&k);  
    74.         //cin>>x>>y>>k;  
    75.         int ans=query(tree_cnt[x-1],tree_cnt[y],k,0,n);  
    76.         //cout<<ans<<endl;  
    77.         printf("%d ",init[ans]);  
    78.         //cout<<init[ans]<<endl;  
    79.     }  
    80.     //cout<<endl;  
    81.     //for(int i=0;i<=n;++i) print(tree_cnt[i],0,n),cout<<endl;  
    82.     return 0;  
    83. }  
  • 相关阅读:
    2020.10.13辗转相除法
    关于……
    友情链接
    李群笔记
    c++中的复数
    python快速傅里叶变换
    多参数函数共轭梯度法寻找极值:gsl_multimin_fdfminimizer
    python: matplotlib.pyplot 制作动图
    简易威尔逊云室计划
    大规模矩阵对角化方法:Lanczos
  • 原文地址:https://www.cnblogs.com/Renyi-Fan/p/8117897.html
Copyright © 2020-2023  润新知