poj2104(主席树讲解)

今天心血来潮,突然想到有主席树这个神奇的玩意儿。。。一直都只是听说也没敢看。(蒟蒻蛋蛋的忧伤。。。)

然后到网上翻大神的各种解释。。。看了半天。。。

一拍脑袋。。。哇其实主席树

真的难。。。【咳咳我只是来搞笑的】

看了很多种解释最后一头雾水啊。。。就是没法脑补出(嗯没错经常脑补数据结构长啥样)主席树的样子。。。。

最后终于找到了一个大大大大大大神犇的ppt,看到了主席树的真面目,才真的能弄懂主席树的结构。。。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

下面进入正题【废话多】

主席树比较简单的理解就是,给你一个长为n的数列,你对于其中每个区间[1,i]都建立一棵线段树,用于保存这个区间中每个数出现的次数(这个显然线段树统计个数吧)。

然后这样你就得到了n棵线段树,如果按正常存储。。。MLE吧

不知道大佬用什么做线段树,反正我是数组模拟的。。。这样预先开好肯定会炸。。。

那么解决的办法呢,当然是有的【又是废话。。。我该改改这毛病了。。。】

用指针建树,就可以消灾解难了。为什么呢,仔细想想,如果说a[i]很小,那么就可以从tree[i-1]中得到很多重复的部分。譬如这n个数字是1~10,a[i]=3,那么显然tree[i]的右半棵子树和tree[i-1]一模一样,就可以直接使用。

如此算来,每加一个数,只要增加log(size)个节点 【注:size为n个数中不重复的个数】。那么只要O(n log n)就够了。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

如果你脑中实在脑补不出(像我一样),那么下面就是图解:

【图中黑色的是原本存在的点,红色是新增的点,可见新增的点权值在(左数第二个黑点)和(最右边的黑点)之间。】

这样一来,应该可以有点概念了吧。。

如此看来,主席树还不算太难理解吧??

先看构造的数据结构。。。

 1 struct node
 2 {
 3     node *son[2];
 4     int cnt;
 5     node(){son[0]=son[1]=NULL;cnt=0;}
 6     void update()
 7     {
 8         if (son[0]) cnt+=son[0]->cnt;
 9         if (son[1]) cnt+=son[1]->cnt;
10     }
11 }*null=new node(),*root[200011]={NULL},q[1850011];

构造一下吧

那么对于主席树的构建,也是好理解的了。只要连边或者加点就好了,复杂度毋庸置疑是O(log n)

 1 void build(node *&y,node *&x,int l,int r,int tmp)
 2 {
 3     if (x==NULL) x=null;
 4     y=&q[++qt];
 5     *y=node();
 6     int mid=l+r>>1;
 7     if (l==r)
 8     {
 9         *y=*x;
10         y->cnt++;
11         return;
12     }
13     if (tmp<=a2[mid])
14     {
15         build(y->son[0],x->son[0],l,mid,tmp);
16         y->son[1]=x->son[1];
17         y->update();
18     }
19     else
20     {
21         build(y->son[1],x->son[1],mid+1,r,tmp);
22         y->son[0]=x->son[0];
23         y->update();
24     }
25 }

憋偷看代码

还有关于查找第k大的方法,和线段树是类似的,由于主席树一个重要的性质就是可减,所以只要同时计算tree[l-1]和tree[r]的同区间节点并相减,然后和k比较,往下递归寻找,直到叶节点为止。复杂度O(log n)。

 1 void find(node *&x1,node *&x2,int l,int r,int k)
 2 {
 3     if (x1==NULL) x1=null;
 4     if (x2==NULL) x2=null;
 5     if (l==r)
 6     {
 7         cout << a2[l] << "\n";return;
 8     }
 9     int mid=l+r>>1,hs=0;
10     if (x2->son[0]) hs+=x2->son[0]->cnt;
11     if (x1->son[0]) hs-=x1->son[0]->cnt;
12     if (hs>=k) find(x1->son[0],x2->son[0],l,mid,k);
13     else find(x1->son[1],x2->son[1],mid+1,r,k-hs);
14 }

看吧看吧

大概就是这么个套路。。。

噢对了还有很重要的一点!!!!

对于size的获得与比较,都应在有序的基础上。所以可以先复制一个数组sort一下,然后unique一下,(就是个离散化)。

p党真不好意思我想你们可能不会看到这里了不过。。。就是个排序去重的意思。

那么对于裸题poj2104而言,代码如下(也许会和神犇有点像。。。)

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>
 4 using namespace std;
 5
 6 int n,m,a[200011],a2[200011];
 7 struct node
 8 {
 9     node *son[2];
10     int cnt;
11     node(){son[0]=son[1]=NULL;cnt=0;}
12     void update()
13     {
14         if (son[0]) cnt+=son[0]->cnt;
15         if (son[1]) cnt+=son[1]->cnt;
16     }
17 }*null=new node(),*root[200011]={NULL},q[1850011];
18 int qt;
19
20 int read()
21 {
22     int x=0,f=1;char c=getchar();
23     while (!isdigit(c)) {if (c==‘-‘)f=-1;c=getchar();}
24     while (isdigit(c)) {x=x*10+c-‘0‘;c=getchar();}
25     return x*f;
26 }
27
28 void build(node *&y,node *&x,int l,int r,int tmp)
29 {
30     if (x==NULL) x=null;
31     y=&q[++qt];
32     *y=node();
33     int mid=l+r>>1;
34     if (l==r)
35     {
36         *y=*x;
37         y->cnt++;
38         return;
39     }
40     if (tmp<=a2[mid])
41     {
42         build(y->son[0],x->son[0],l,mid,tmp);
43         y->son[1]=x->son[1];
44         y->update();
45     }
46     else
47     {
48         build(y->son[1],x->son[1],mid+1,r,tmp);
49         y->son[0]=x->son[0];
50         y->update();
51     }
52 }
53
54 void find(node *&x1,node *&x2,int l,int r,int k)
55 {
56     if (x1==NULL) x1=null;
57     if (x2==NULL) x2=null;
58     if (l==r)
59     {
60         cout << a2[l] << "\n";return;
61     }
62     int mid=l+r>>1,hs=0;
63     if (x2->son[0]) hs+=x2->son[0]->cnt;
64     if (x1->son[0]) hs-=x1->son[0]->cnt;
65     if (hs>=k) find(x1->son[0],x2->son[0],l,mid,k);
66     else find(x1->son[1],x2->son[1],mid+1,r,k-hs);
67 }
68
69 int main()
70 {
71     null->son[0]=null;null->son[1]=null;
72     n=read();m=read();
73     for (int i=1;i<=n;i++)
74     {
75         a[i]=read();
76         a2[i]=a[i];
77     }
78     sort(a2+1,a2+1+n);
79     int sz=unique(a2+1,a2+1+n)-(a2+1);
80     for (int i=1;i<=n;i++)
81         build(root[i],root[i-1],1,sz,a[i]);
82     for (int i=1;i<=m;i++)
83     {
84         int ll,rr,kk;
85         ll=read(),rr=read(),kk=read();
86         find(root[ll-1],root[rr],1,sz,kk);
87     }
88     return 0;
89 }

时间: 2024-11-02 05:55:58

poj2104(主席树讲解)的相关文章

poj2104(主席树)

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

poj2104 主席树 区间K大 在线 无修改

关于主席树: 主席树(Chairman Tree)是一种离线数据结构,使用函数式线段树维护每一时刻离散之后的数字出现的次数,由于各历史版本的线段树结构一致,可以相减得出区间信息,即该区间内出现的数字和对应的数量,由于在线段树内,左子树代表的数字都小与右子树,便可像平衡树一样进行K大询问.新建一颗树是\(O(logn)\),查询一次也为\(O(logn)\). 比划分树好想&写多了,但是在POJ上比划分树慢一些. CODE: 1 #include <cstdio> 2 #include

POJ2104主席树模板题

完成新成就——B站上看了算法https://www.bilibili.com/video/av4619406/?from=search&seid=17909472848554781180#page=2 K-th Number Time Limit: 20000MS   Memory Limit: 65536K Total Submissions: 60158   Accepted: 21054 Case Time Limit: 2000MS Description You are working

主席树讲解

以下转自http://prominences.weebly.com/1/post/2013/02/1.html 可持久化线段树,也叫作函数式线段树,也就是主席树,(...因为先驱就是fotile主席..Orz...)网上的教程很少啊,有的教程写得特别简单,4行中文,然后就是一篇代码--这里,我将从查找区间第k小值(不带修改)题的可持久化线段树做法中,讲一讲主席树./*只是略懂,若有错误,还请多多包涵!*/可持久化数据结构(Persistent data structure)就是利用函数式编程的思

【POJ 2104】 K-th Number 主席树模板题

达神主席树讲解传送门:http://blog.csdn.net/dad3zz/article/details/50638026 2016-02-23:真的是模板题诶,主席树模板水过.今天新校网不好,没有评测,但我立下flag这个代码一定能A.我的同学在自习课上考语文,然而机房党都跑到机房来避难了\(^o^)/~ #include<cstdio> #include<cstring> #include<algorithm> #define for1(i,a,n) for(i

主席树(函数式线段树)学习小结(附手绘讲解图片)

主席树是一种离线数据结构,是由很多棵线段树组成的. 第i棵线段树存的是前i个数的信息: 每一个线段存数字的出现次数(因此建树之前要离散化). 那么n棵线段树不会MLE吗? 当然会了! 但是我们发现第i棵线段树和第i-1棵线段树是非常相似的,有许多结点完全相同,因此可以借用之前的结点,没必要新建结点. 具体建树方法建下图: 序列为 1 3 4 2 那么如果要询问i-j之间数字出现的次数怎么办呢? 因为每一棵线段树的区间都是相同的,所以要求l-r之间的数字的出现次数只要用前r位出现的次数减去前l-1

【POJ2104】K-th Number——主席树

早上刷NOIP的题刷到有点烦就想学点新东西,然后.....一个早上就这样过去了QAQ.虽然主席树不是NOIP考点,但是...或许我能活到省选呢?(美好的幻想) 题目链接 题目的大意就是给定一个长度为n的区间,给出m个询问,每次询问一个区间[l,r]中第k小的树. 主席树(一种可持久化线段树)的入门题. 推荐一发学习资料:戳这里 感觉人家讲得很仔细了我也没什么讲的必要了...... 总算是学了一种可持久化树了,好像也没想象中那么难?这道题的重点在query函数方面,建议自己在纸上模拟一下建树和查询

[poj2104]可持久化线段树入门题(主席树)

解题关键:离线求区间第k小,主席树的经典裸题: 对主席树的理解:主席树维护的是一段序列中某个数字出现的次数,所以需要预先离散化,最好使用vector的erase和unique函数,很方便:如果求整段序列的第k小,我们会想到离散化二分和线段树的做法, 而主席树只是保存了序列的前缀和,排序之后,对序列的前缀分别做线段树,具有差分的性质,因此可以求任意区间的第k小,如果主席树维护索引,只需要求出某个数字在主席树中的位置,即为sort之后v中的索引:若要求第k大,建树时反向排序即可 1 #include

POJ2104 K-th Number[主席树]

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