主席树

主席树入门+博客推荐

匿名 (未验证) 提交于 2019-12-02 23:55:01
主席树是就是多颗线段树的总结 主席树的结构体中的 \(l\) 和 \(r\) 代表的是这个节点的左右子节点的标号,因为空间优化的原因,他们可能不再符合左子树编号等于 \(rt<<1\) ,右子树编号等于 \(rt<<1|1\) ,这也是我开始比较困惑的一点。在学习主席树之前,需要你很熟悉线段树这个东西,因为主席树的主体是多颗线段树,首先我们来简单的回顾一下线段树的简单特点和性质,我们熟悉的线段树一般是用一个结构体表示一个节点,每个节点有一个编号,节点里面一般有两个变量l, r来表示这个节点维护的区间,然后还有一个区间信息(比如区间最大值,最小值,和等,视具体问题而定),当然如果涉及到区间更新,还有一个add或者lazy叫做延迟标记的东西,然后一般线段树最明显的特点就行,一个父节点的编号是i,那么他的两只儿子节点的编号分别为2 * i(左) , 2 * i + 1(右),注意主席树在这一点有别于一般的线段树,每一个父节点,他的两个儿子节点的编号不一定满足这个关系。 主席树的结构体数组需要开多大呢?根据主席树的空间复杂度(这个我不会,我看的博客上的)是 \(n*logn\) ,看的博客上右开 \(n\) 的 20倍和40倍的,这里还是最好算一下,开40倍就很大了。 POJ 2104 K-th Number ,我也写了关于这个题的博客,代码有注释,可以方便理解。 #include

主席树

匿名 (未验证) 提交于 2019-12-02 23:52:01
主席树是一种可持久化权值线段树,用于解决可查询历史版本的线段树的空间问题。 基于主席树的静态区间第K小问题要点如下: 建立 \(n\) 颗权值线段树,其中以 \(root[i]\) 为根的第 \(i\) 颗线段树储存 \([l, r]\) 区间内的权值信息 每一次新增一个结点,最多增加 \(log_2N\) 个结点 区间信息具有可加性,即 \([4,10]\) 区间内的结点个数等与 \([1,10]\) 减去 \([1,3]\) 查寻第 \(k\) 小,只需要根据区间内结点数多少二分第 \(k\) 小所在区间即可 Luogu P3834 说明 主席树空间计算:由要点2可以得出,至少需要 \(N * log_2(4 * N)\) 个结点 #include <cstdio> #include <algorithm> const int maxn = 2e5 + 10; int a[maxn], sortArray[maxn], b[maxn]; int root[maxn], t[maxn << 5][2], val[maxn << 5], cnt; #define lch(x) t[x][0] #define rch(x) t[x][1] template <class T> inline void read(T &x) { x = 0; int ch = getchar();

近期上课的题目+相关题目

匿名 (未验证) 提交于 2019-12-02 23:49:02
pdbs中priority_queue: 【BZOJ3040】 左偏树: 【BZOJ2809】 树链剖分: 【NOIP2015】 点分治: 【POJ1741】 动态点分治: 【ZJOI2007】 莫队: 【国家集训队】 CDQ分治,整体二分,主席树并没有例题。 【BZOJ4361】 , 【CF283E】 , 【CF436E】 , 【HNOI2016】 , 【HNOI2017】 。 WC2014 紫荆花之恋 WC2013 糖果公园 WC2015 k小割(这个倒是可以不做,没太大含义) WC2018 通道 UOJ119 决战圆锥曲线(不难) UOJ191 Unknown UOJ207 共价大爷游长沙 清华集训2015 V 清华集训2016 Alice和Bob又在玩游戏 ZJOI2016 大森林 ZJOI2017 树状数组 ZJOI2017 线段树 ZJOI2018 历史 CTSC2016 时空旅行 CTSC2017 游戏 CTSC2018 暴力写挂 NOI2017 蔬菜 APIO2013 TOLL lct。 动态dp。 kd-tree。 圆方树,仙人掌。 可持久化(不只是主席树)。 字符串相关(应该是cjk讲吧):AC自动机,回文自动机,后缀自动机(构后缀树)。 这一两年出现的新数据结构(也许出现了)。 【ZJOI2015】 , 【BZOJ3730】 , 【BZOJ4372】 持续更新

模板―主席树(待修改)

匿名 (未验证) 提交于 2019-12-02 23:43:01
这个有点复杂,按理应该写写的……下次再说吧 #include<iostream> #include<cstdio> #include<algorithm> using namespace std; struct node { int l,r,sum; #define l(x) tr[x].l #define r(x) tr[x].r #define sum(x) tr[x].sum }tr[10000000]; struct que { char a;int l,r,kth; }qq[20010]; int cnt,m,ce,T[20010],S[20010]; int n,Q,a[20010],b[20010]; int use[3][20010]; inline int lowbit(int x){return x&(-x);} inline int build(int l,int r) { int now=++cnt; if(l==r)return now; int mid=(l+r)>>1; l(now)=build(l,mid); r(now)=build(mid+1,r); return now; } inline int build_new(int flag,int mark,int loc,int val) { int rt=++cnt; int before;

模板―主席树

匿名 (未验证) 提交于 2019-12-02 23:39:01
#include<bits/stdc++.h> using namespace std; const int maxn=2e6+7; int n,m,q; int a[maxn]; int b[maxn]; int root[maxn]; int lc[maxn<<5]; int rc[maxn<<5]; int sum[maxn<<5]; int l,r,k,p; int cnt; void build(int &t,int l,int r) { t=++cnt; if(l==r) return; int mid=(l+r)>>1; build(lc[t],l,mid); build(rc[t],mid+1,r); } int modify(int o,int l,int r) { int oo=++cnt; lc[oo]=lc[o],rc[oo]=rc[o],sum[oo]=sum[o]+1; if(l==r) return oo; int mid=(l+r)>>1; if(p<=mid) lc[oo]=modify(lc[oo],l,mid); else rc[oo]=modify(rc[oo],mid+1,r); return oo; } int query(int u,int v,int l,int r,int k) { int ans,mid=(l+r)>>1,x

主席树再探

匿名 (未验证) 提交于 2019-12-02 23:03:14
(零基础者出门左拐) 最近学了主席树,打了几道模板题。 感觉还行 主席树,在我看来就是线段树的可持化 (一开始以为主席树只是可持久化权值线段树) 。在题目中需要建多颗线段树或权值线段树且,相邻的线段树差别不大(一般就一个点不一样)时就可以用主席树。运用可持久化的思想,我们并不需要重新构建一颗线段树,因为只需要改一个点,所以线段树只需要新多出 \(logn\) 个节点,其他的节点继承前面的线段树就行了(所以一般都要开始建一颗空树)。这样一来,我们建树的时间复杂度和、这一堆线段树的空间复杂度变成了 \(nlogn\) ,真是佩服人类的智慧。 嗯,模板题,求区间第k大,我们找到对应的两颗线段树 \(root[r]\) 和 \(root[l-1]\) 然后在这两颗线段数上同时二分就行了。 #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int N=2e5+100; int num,n,m,a[N],b[N],tot,root[N],w[N*20],ch[N*20][2]; void build(int l,int r,int &now){ now=++num; if(l==r)return; int mid

主席树

安稳与你 提交于 2019-12-02 16:52:47
目录: 个人理解 时空复杂度分析 应用及例题 拓展 一、个人理解: 主席树 的全称是 可持续化权值线段树 ,是一种可以维护静态区间第K小的高级数据结构。 主席树的主要思想就是:保存每次插入操作时的历史版本,以便查询区间第 \(k\) 小。 因为主席树每次都要插入操作,所以是不能用 堆式建树 的( rt<<1 , rt<<1|1 ),所以我们使用 动态开点线段树 ,并用 ls[] 和 rs[] 保存当前节点的左右儿子。 联系 前缀和 ,可以预处理达到 \(O(1)\) 的时间复杂度。我们发现主席树也满足这个性质,所以若需要统计 \([l,r]\) 的信息,只需要 \(O(1)\) 查询 sum[r]-sum[l-1] 即可。 二、时空复杂度分析: 时间复杂度: 同线段树的时间复杂度: \(O(n\text{log}n)\) 空间复杂度: 我们是动态开点,所以一颗线段树只会出现 \(2n-1\) 个节点,而每次插入会增加 \(O(n\text{log}n)\) 个节点,则最坏情况下会有 \(2n-1+O(n\text{log}n)\) 个节点。故空间复杂度为: \(O(n\text{log}n)\) 。 在实际运用的时候, ls[] , rs[] , sum[] 等数组都需要开 \(2^5\) 倍空间,即 MAXN<<5 ( MAXN 为 \(n\) 的值域)。 注

牛客 情报传递 树剖+主席树

让人想犯罪 __ 提交于 2019-12-02 16:33:33
题目链接: https://ac.nowcoder.com/acm/problem/20570   要统计X到Y上的点的数量以及危险值大于C的点的数量,对树上两点的路径进行查询可以用树链剖分,在某一个时间点t,我们要求出X到Y路径上危险值大于C的点的数量,我们可以把它转化为求在时间t-C-1之前开始收集情报的情报员数量,因为一旦开始收集危险值就会一直增加,所以在时间t-C-1之前开始收集情报的点的危险值一定是大于C的。   这里我们可以使用主席树,我的做法是对每一个时间点都用动态开点建一棵线段树,如果k==2,那么在时间点为t的这颗树上,情报员T的值要赋值为1,说明情报员从t时间点开始收集情报了,如果k==1,那么我们需要的是查找,但是我这里因为要每一个时间点都创建一棵线段树,所以我们可以让root[t]=root[t-1],即让时间点为t的这个树指向时间点为t-1的这颗树的根节点,这样两颗树就是一模一样的了。然后再让时间点为0和t-C-1的这两颗线段树相减,两颗树两两对应的点相减,类似于求前缀和,我们只要用树剖查找X和Y路径上所有的点在两颗树相减之后得到的线段树(这颗线段树代表的时间就是1到t-C-1这一段时间)中值为1的个数就行了。 代码: #include<iostream> #include<cstring> #include<algorithm> #include

主席树模板

老子叫甜甜 提交于 2019-12-02 13:13:09
简述:    解决线段树无法求区间第k大的问题 代码: 1 ///主席树模版(查询区间第k大) 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cstring> 5 #include <algorithm> 6 #include <cctype> 7 #define numm ch-48 8 #define pn putchar('\n') 9 #define pd putchar(' ') 10 //#define debug(args...) cout<<#args<<"->"<<args<<endl 11 //#define bug cout<<"*******************"<<endl 12 using namespace std; 13 template<typename T> 14 void read(T &res) { 15 char ch;bool flag=false; 16 while(!isdigit(ch=getchar()))(ch=='-')&&(flag=true); 17 for(res=numm;isdigit(ch=getchar());res=(res<<1)+(res<<3)+numm); 18 flag&&(res=-res); 19 } 20 template

【学习笔记】主席树

烂漫一生 提交于 2019-12-02 11:07:59
概述 就是可持久化的线段树。更直白一点,就是 很多个线段树套在一起 (共用信息相同的节点)。 干嘛 可以解决区间第 k k k 大等神奇问题。 优点 快。 实现 0x01初始化 一开始,我们需要一颗线段树。就按照普通的建树方法即可。 0x02建新树 要素是 不改变原有的节点 。因为本质是很多不同的线段树,只是用一些奇技淫巧来省空间(和时间)而已。 首先,将原来的树的 对应节点复制到当前节点 。包括子节点是谁。也就是说,现在这两个点 共用了相同的子节点 。 然后考虑子节点——如果有修改,则 申请新节点,递归的建树 。否则不动。 在理解这个玩意儿的时候,你就这么想:其实两个点的子节点不同,没有共用子节点,只是子节点住一间屋子而已。反正我们也不会把房子拆了,即修改原有的节点。 也就是说,本质是这样的两颗线段树。绿色的编号表示 内存地址 。共用内存罢了。 ——前提条件是这两个节点 信息相同 ! 这样做的好处,在 单点修改 时很明显:只需要新建 log ⁡ n \log n lo g n 个点。因为只有 log ⁡ n \log n lo g n 这一条单链上的点(即红色的点)与原有的节点信息不同。 或者放点代码? void modify ( int old , int & o , int id ) { o = cntNode ++ ; // 申请新节点 node [ o ] = node