清北学堂noip2018集训D2
清北学堂noip2018集训D2
P.S.
今天讲数据结构,大概包括了二叉堆,二叉搜索树,线段树,树状数组,并查集,st表,RMQ,LCA。
二叉堆
- 基本功能
- O(log n)插入元素
- O(1)查最大(最小)元素
- O(log n)删除最大(最小)元素
- O(log n)删除任意元素
- 基本结构
- 最为常用的堆结构就是完全二叉堆。
- 在逻辑形式上,结构必须等同于完全二叉树,同时,堆顶以外的每个节点都不大于(小于)其父亲,即堆有序。
- 如果堆顶最大,称为最大二叉堆(大根堆),反之称为最小二叉堆(小根堆)。
- 因为完全二叉堆等同于完全二叉树的结构,故高度相当。为O(log n).
- 一般为了实现方便,根节点编号为1,节点n左节点为2n,右节点为2n+1.
查询操作
- 最大(最小),直接返回堆顶即可。
插入操作
- 首先把新元素插入向量末尾,为了维持堆的有序性,逐层检查是否有序并调整,过程通常称为上滤。
建堆
- O(n log n)插入每个元素即可。
- O(n)?
- 先随机放入一个完全二叉树。
- 从倒数第二个深度开始检索,使堆有序。
删除最大(最小)元素
- 首先将堆顶元素与堆尾元素交换。
- 删除堆尾元素。
- 一步一步将这个换上去的元素下移。
- 直到堆有序。
删除任意元素
- 新建一个堆,将删除的元素放入新堆里。即可实现删除。
二叉搜索树
- 基本功能
- 排序
- 查询元素大小排名
- 插入元素
- 二叉平衡树的基础
- 结构
- 若左子树不为空,则左子树上所有节点小于根节点。
- 若右子树不为空,则右子树上所有节点大于根节点。
- 左右子树也分别称二叉排序树。
- 排序
- 中序遍历
- 查询元素大小排次
- 从根开始向左、右是唯一的,如果向右,那么将左边的节点数加起来,反之不加。
- 插入
- 与查询方法类似。
- 查询到节点后向上更新其他节点。
线段树
- 基本功能
- 建树
- 单点查询
- 单点修改
- 区间查询
- 区间修改
- 结构
- 是一棵二叉搜索树,每个节点代表一个区间。
- 每个节点需要维护
- 区间左右端点
- 区间要维护的信息
- 基本思想:二分
- 特殊性质
- 左子节点区间为[l,mid],右为[mid+1,r].
- 对于一个节点k,左子节点为2k,右子节点为2k+1.
- 建树
- 对于二分的每一个节点,确定其代表的区间。
- 如果为叶子结点,存需要维护的信息。
- 对于非叶子结点,将两个子节点状态合并。
- 单点查询
- 在左右节点搜索是确定的,由节点的mid决定,如果要查询的点大于mid,则在右子树搜索,反之在左子树搜索。代码如下:
#include<bits/stdc++.h> using namespace std; #define LL long long const int MAXN = 100000; struct tree{int l,r,s; }mi[4*MAXN+20]; void build(int k,int l,int r){mi[k].l=l;mi[k].r=r;if(l==r){mi[k].s=r;return;}int mid=(l+r)/2;build(2*k,l,mid);build(2*k+1,mid+1,r);mi[k].s=mi[k*2].s+mi[k*2+1].s; } int search(int k,int n){if(mi[k].l==mi[k].r&&mi[k].r==n){return k;}int mid=(mi[k].l+mi[k].r)/2;if(n<=mid) return search(k*2,n);else return search(k*2+1,n); } int main(){build(1,1,6);cout<<search(1,5);return 0; }
- 在左右节点搜索是确定的,由节点的mid决定,如果要查询的点大于mid,则在右子树搜索,反之在左子树搜索。代码如下:
- 单点修改
- 先执行单点搜索,然后执行修改操作,再向上更新状态。
- 代码如下
int insert(int k,int n,int v){int now=search(k,n);mi[now].s+=v;while(now!=1){now/=2;mi[now].s=mi[2*now].s+mi[2*now+1].s;} }
- 区间查询
- 令[l,r]为当前节点代表的区间,mid代表区间中点,[x,y]代表要查询的区间。
- 若[l,r]=[x,y]直接返回即可。
- 如果y≤midy\leq midy≤mid,[l,r]->[l,mid],[x,y]不变,递归查询。
- 如果x>midx > midx>mid,[l,r]->[mid+1,r],[x,y]不变,递归查询。
- 否则,[x,y]跨过了mid的两端,将其分成了两个子问题,[l,mid][l,mid][l,mid]中查[x,mid][x,mid][x,mid].[mid+1,r][mid+1,r][mid+1,r]中查[mid+1,y][mid+1,y][mid+1,y].
- 代码如下:
int query_sum(int k,int x,int y){if(y<mi[k].l||x>mi[k].r) return 0;if(x==mi[k].l&&mi[k].r==y) return mi[k].s;int mid=(mi[k].l+mi[k].r)/2;return query_sum(k*2,x,mid)+query_sum(k*2+1,mid+1,y); }
- 区间修改
- 关键:懒标记
- 经过节点的集合与区间查询相同
- 更新这些区间的答案并打上一个标记,来表示这个区间中的数要进行哪些修改。
- 只有当查询或者修改经过一个节点时,才将其节点上的懒标记放到两个孩子节点,下放的同时修改两个孩子节点的答案。
模板(无区间修改)
/*求和线段树*/
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define inf 2147483647
const int MAXN = 100000;
struct tree{int l,r,s;
}mi[4*MAXN+20];
void build(int k,int l,int r){mi[k].l=l;mi[k].r=r;if(l==r){mi[k].s=r;return;}int mid=(l+r)/2;build(2*k,l,mid);build(2*k+1,mid+1,r);mi[k].s=mi[k*2].s+mi[k*2+1].s;
}
int search(int k,int n){if(mi[k].l==mi[k].r&&mi[k].r==n){return k;}int mid=(mi[k].l+mi[k].r)/2;if(n<=mid) return search(k*2,n);else return search(k*2+1,n);
}
int query_sum(int k,int x,int y){if(y<mi[k].l||x>mi[k].r) return 0;if(x==mi[k].l&&mi[k].r==y) return mi[k].s;int mid=(mi[k].l+mi[k].r)/2;return query_sum(k*2,x,mid)+query_sum(k*2+1,mid+1,y);
}
int insert(int k,int n,int v){int now=search(k,n);mi[now].s+=v;while(now!=1){now/=2;mi[now].s=mi[2*now].s+mi[2*now+1].s;}
}
int main(){int n,m,x,v;cin>>n>>m>>x>>v;build(1,n,m);cout<<query_sum(1,x,v);return 0;
}
树状数组
- 基本功能
- 单点修改,前缀信息查询。
- 区间修改可减信息(前缀和),单点查询。
- lowbit函数
- 含义:一个数,二进制表示中的最后一个1。
- 计算机中负数的表示?
- 怎么求lowbit呢?
- xand(−x)x and (-x)xand(−x)
- 结构
- 我们定义c[i]为区间[i−lowbit(i)+1,i][i-lowbit(i)+1,i][i−lowbit(i)+1,i]。
- 怎么直观的看一看呢?
- Sn=S(n−lowbit(n))+CnS_n=S_{(n-lowbit(n))}+C_nSn=S(n−lowbit(n))+Cn
- 查询代码
int sum(int x){int ans=0;for(;x;x-=lowbit(x)) ans+=c[x];return ans;
}
- 修改代码
void update(int x,int v){for(;x<=n;x+=lowbit(x)) c[x]+=v;
}
- 代码示例:
#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
int a[maxn],c[maxn];
int n;
int lowbit(int x){return x&(-x);
}
int sum(int x){int ans=0;for(;x;x-=lowbit(x)) ans+=c[x];return ans;
}
void update(int x,int v){for(;x<=n;x+=lowbit(x)) c[x]+=v;
}
int main(){cin>>n;for(int i=1;i<=n;i++){cin>>a[i];update(i,a[i]);}cout<<sum(n);return 0;
}
- 区间修改,单点查询
- 且以修改为加法,查询为求和为例。
- 对于将[l,r][l,r][l,r]加xxx的操作,可以将点lll加xxx,将点r+1r+1r+1减xxx,这时候求点nnn的前缀和就能得到点nnn真实的值。
并查集
- 基本功能
- 合并两个集合
- 查询某一个元素属于哪个集合
- 结构
- 并查集S由若干子集合SiS_iSi构成,并查集的逻辑结构是一个森林。
- SiS_iSi表示森林中的一颗子树。
- 一般以子树的根作为该子树的代表。
- 而对于并查集的存储结构,可用一维数组来实现。
- 查询某个元素属于哪个集合
- 通常而言都是以根作为这个集合的代表元。
- 因此只需要不断向父亲节点走,直到走到根为止返回即可。
- 合并集合
- 路径压缩优化
- 示例代码
#include<bits/stdc++.h> using namespace std; int fa[200010]; int n; int find(int x) {if(fa[x] == 0) return x;return fa[x] = find(fa[x]); } void add(int x,int y){int xx=find(x),yy=find(y);fa[yy]=xx; } int main(){cin>>n;int x,y,m,z;for(int i=0;i<n;i++){cin>>x>>y;add(x,y);}cin>>m>>z;cout<<find(m)<<" "<<find(z);return 0; }
RMQ问题
- R——Range
- M——Minimum/Maximum
- Q——Query
- 即区间最值问题
例题
- 方法1:使用线段树
- 方法2:st表
ST表
- 基本功能
- O(nlogn)O(nlogn)O(nlogn)预处理,O(1)O(1)O(1)查询区间最值。
- 结构
- f[i][j]f[i][j]f[i][j]表示区间[i,i+2j−1][i,i+2^j-1][i,i+2j−1]的信息。
- 整个fff数组就是ST表。
- 建表
- f[i][0]f[i][0]f[i][0]就是单点i的信息
- 当j>0j>0j>0时,f[i][j]f[i][j]f[i][j]为f[i][j−1],f[i+2j−1−1][j−1]f[i][j-1],f[i+2^{j-1}-1][j-1]f[i][j−1],f[i+2j−1−1][j−1]两个区间信息的并集。
- 查询
- 比方说我们要查询区间[x,y][x,y][x,y]的信息。
- 令t为最大的正整数使得2t≤y−x+12^t\leq y-x+12t≤y−x+1
- 那么答案就是[x,x+2t−1]∪[y−2t+1,y][x,x+2^t-1]\cup[y-2^t+1,y][x,x+2t−1]∪[y−2t+1,y]
- 代码如下:
#include<bits/stdc++.h>
#define maxn 111100
#define logN 25
//因为cmath中的log函数效率差,不如直接设定一个永远到不了的值
using namespace std;int f[maxn][logN],a[maxn],n,m; void pre_st(){ //制表 for(int i=1;i<=n;i++)f[i][0]=a[i]; //因为第二个框框中存的j是用来计算 i+2^j-1(既该f保存的值) //服务于动态规划的结果 for(int j=1;j<=logN;j++){for(int i=1;i+(1<<j)-1<=n;i++)f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]); //注释1 (1<<j)是计算出 2^j 把一一直右移即可得到//注释2 使用刚才得到的动态规划方程 }
}int main(){cin >> n;cin >> m;for(int i=1;i<=n;i++) scanf("%d",&a[i]);pre_st(); //制表 int x,y; for(int i=1;i<=m;i++){ cin >> x >> y;int s=log(y-x+1)/log(2); //计算出这一段距离之中最大的2的倍数,以查表 cout << max(f[x][s],f[y-(1<<s)+1][s]) << endl;; //合并左右不分的解 }return 0;
}
查询树上两点的最近公共祖先(LCA)
- L——Lowest
- C——Common
- A——Ancestor
- 即最近公共祖先
欧拉序
- 一种特殊的dfs序,每到达一个点就加入序列。
- 记录每个点第一次出现的时间S[u]S[u]S[u],欧拉序中第i个点为Q[i]Q[i]Q[i]。
- LCA(x,y)LCA(x,y)LCA(x,y)是Q[S[x]...S[y]]Q[S[x]...S[y]]Q[S[x]...S[y]]中层数最低的点。
- 区间最值?
- ST表!
清北学堂noip2018集训D2相关推荐
- 清北学堂noip2019集训D2——数据结构
基本数据结构 栈 STL:stack 定义:stack a; 查询堆顶:a.top(); 压入栈顶:a.pop(); 查询a中的元素个数:a.size(); 清空只能慢慢pop. 例题1 给定一个栈, ...
- 清北学堂----北京集训
7月16日 集训第一天,毛晗杨给我们讲的基础算法和数论.基础算法讲了分块和三分等知识点,分块之前接触过,个人认为和莫队有一定的相似之处,都是把整个数组分为一个个大小相等的块,然后对块进行操作,每个块大 ...
- 清北学堂noip2019集训D6——动态规划
状态压缩DP 最短哈密顿回路问题 给定一个完全图,带正边权w(u,v)w(u,v)w(u,v).求出一个顶点的排列v1,v2,...,vnv_1,v_2,...,v_nv1,v2,...,vn, ...
- 清北NOIP训练营集训笔记——图论(提高组精英班)
清北NOIP训练营集训笔记--图论(提高组精英班) 本文摘自清北学堂内部图论笔记,作者为潘恺璠,来自柳铁一中曾参加过清北训练营提高组精英班,笔记非常详细,特分享给大家!更多信息学资源关注微信订阅号no ...
- 2020清北学堂秋季营感想——Hoarfrost
2020清北学堂秋季营感想 前言:九月三十日放假以后,就马不停蹄地开始了这一次的奥赛培训.原先参加过暑假的提高组腾飞营,当时第一场模拟赛拿了第一,便觉得CSP的题目难度不会很高,普及+/提高-左右的难 ...
- 2017国庆 济南清北学堂 8天乐
Day 0 想吐槽济南的堵车 大约接近4点从莱芜走的,走高速一个多小时,5点左右就到了济南,但是因为济南的堵车,下班高峰期,用了两个多小时才到了我们的目的地.好不容易到宾馆登记了,mc还要我们开会,8 ...
- 清明培训 清北学堂 DAY1
今天是李昊老师的讲授~~ 总结了一下今天的内容: 1.高精度算法 (1) 高精度加法 思路:模拟竖式运算 注意:进位 优化:压位 程序代码: #include<iostream> #i ...
- 清北学堂 2017-10-07
********今天得了在清北的最高分,有点开心wwwww,mjy爱您! 树 [问题背景] zhx 是二叉查找树大师. [问题描述] 二叉查找树是一种特殊的二叉树(每个节点最多只有两个儿子的树 ...
- 【十一日记】 清北学堂酱油记
Day 0 五点起床 困~ 喜闻乐见的六点集合 嗯我们出发的时候迟到了四分钟( 06:26 第一次出去怎么多人 啧啧啧 @57的熊孩子们好多啊qwq 十分钟到一中 果然老司机233 嗯 目睹他们放学吃 ...
- 2017清北学堂(提高组精英班)集训笔记——基础算法
我这更新笔记的顺序有点乱时间也很乱,见谅,(其实我是想偷懒什么简单先更什么O(∩_∩)O~) 一.倍增算法: 定义:用f[i][j]表示从i位置出发的2j个位置的信息综合(状态) 一个小小的问题:为什 ...
最新文章
- JS动态生成的元素,其对应的方法不响应(比如单击事件,鼠标移动事件等)...
- 多线程-多图下载综合案例-SDWebImage框架实现代码
- 生成android_Android开发:生成桌面快捷方式是这样做的
- c语言for循环说课稿,C语言FOR循环说课稿.docx
- layui表格弹窗修改_layUI 实现自定义弹窗
- SQL server经典电子书、工具和视频教程汇总
- Java数据结构-基于数组的栈和泛型
- Redis集群明细文档(转)
- 滚屏加载--无刷新动态加载数据技术的应用
- linux centos7 cuda安装
- 常见 Java 异常解释(恶搞版)
- Oracle触发器3-DDL触发器
- 2019-06-02 Java学习日记之多线程上
- NF、$NF、FR、FNR举例说明
- 如何学好Python自动化,每个进阶的测试人都应该看看
- CKEditor4x word导入不保存格式的解决方案
- 【STM32H7的DSP教程】第5章 Matlab简易使用之常用编程语句
- 计算机应用软件论文范文,计算机论文范文大全集(计算机应用论文范文5000字)
- 数商云:分享国内快消品B2B平台的仓储物流模式
- P3224 [HNOI2012]永无乡 FHQ-Treap 启发式合并
热门文章
- 电脑开热点手机无法连接,无法启用 Internet 连接共享.为 LAN 连接配置的 IP 地址需要使用自动 IP 寻址.
- 分享几个简单的HTML网页特效代码
- 卸载计算机系统,W7电脑系统如何卸载软件
- 怎么查看计算机mac地址,怎么查电脑的mac地址?电脑mac地址查询方法
- 庆祝61-牛客模拟笔试七月场(使圆圈队形中相邻小朋友的身高差的最大值最小的解法)
- python图片爬虫,指定关键字爬取Google图片+搜狗图片
- 英文邮件中常见的英语缩写
- 硬盘分区被格式化了如何恢复
- Ubuntu下deb包的安装方法
- 微信公众号获取永久素材