二叉堆
目的:快速获得队列中最大的,STL打法解决问题
基本功能
- 在O(logN)插入元素
- 在O(1)中访问堆顶元素
- 在O(logN)删除最大
- 在O(logN)删除任意元素
结构
- 最为常用的对结构,本质是完全二叉堆,高度为 log(n)
- STL中有Priority Queue快速实现
- 有序性,爸爸都比孩子大
- n节点的左右孩子分别为2n、2n+1
操作
- 查询: 最大或最小值从堆顶返回,O(1)
- 插入(上浮): 先插入队尾,与前n/2的元素(父节点)比较,最差不过O(log N)
- 建堆: 直接按照数组顺序读入(不需要按次),之后从倒数第二行开始(n/2节点处) 向自己的子节点尝试下移,如果下移成功,子节点需要继续向上判断,最差最高根节点
- 删除堆中元素:最优方法O(n):新建labor堆,标记已经删去的元素->目的:不破坏原来的堆结构。当查询的时候顺便修复
- 删除堆顶元素(下沉):先与末尾元素交换,再由堆顶向下交换
本蒟蒻还不会正经二叉堆,所以优先队列搞起来~
优先队列
priorityQueue ,STL拯救我
特征:自动排序,赛高!解决我不会二叉树的最简单方法
文章部分摘自https://blog.csdn.net/c20182030/article/details/70757660
- 初始化1:
priority_queue <int> i;
priority_queue <double> d;
- 初始化2:
priority_queue<vector<int>, less<int> > pq1;
priority_queue<deque<int>, greater<int> > pq2;
注意:除了使用less<>,greater<>其他的默认大的在前
- 初始化三:
struct node(){
};
bool cmp(node x,node y){
return ...<....;
}
priority_queue <node,vector<node>,cmp>q;
- 初始化三:
struct node {
int x, y;
friend bool operator < (node a, node b)
{
return a.x > b.x; //结构体中,x小的优先级高
}
};
priority_queue<node>q;
- API:
q.size();//返回q里元素个数
q.empty();//返回q是否为空,空则返回1,否则返回0
q.push(k);//在q的末尾插入k
q.pop();//删掉q的第一个元素
q.top();//返回q的第一个元素
q.back();//返回q的末尾元素
简单的使用:
#include<iostream>
#include<queue>
using namespace std;
priority_queue<int> q;
int main(){
int n,tmp;
cin >> n;
for(int i=1;i<=n;i++){
cin >> tmp;
q.push(tmp);
}
while(!q.empty()){
tmp=q.top();
q.pop();
cout << tmp << endl;
}
}
线段树
今日攻占的核心中的核心!!
用途:查找连续的区间的时候的快速骚操作
基本功能:
1、建树
2、单点查询
3、单点修改
4、区间查询
5、区间修改
结构:
1、线段树是一棵二叉搜索树,储存区间信息
2、每个断点要维护:
· 区间左端点和右端点
· 题目所需要的信息
3、基本思想:二分
4、特殊性质:左孩子范围为[l,mid],右端为[mid+1,r]
对于借点k,左孩子为2k,右孩子2k+1
模板代码(QWQ不会用):
#include<iostream>
#include<cstdio>
#define maxn 1000000
using namespace std;
int a[maxn],min[4*maxn],n;
//建树操作
void build(int pos,int l,int r){ //pos当前节点编号、l,r代表当前节点所代表区间
if(l==r){
min[pos]=a[pos];
return;
}
int mid=(l+r)>>1;
build(k*2,l,mid);
build(k*2+1,mid);
min[pos]=min(mi[k*2],min[k*2+1]);
}
//区间问询操作
int query_min(int pos,int l,int r,int x,int y){ //pos当前节点位置,l,r当前节点左右,x,y查询节点区间
if(x<l||x>r) return 9999999; //返回极大值代表无信息
if(x<=1&&r<=y) return min[k];
int mid=(l+r)>>1;
return min(query_min(k*2,l,mid,x,y),query_min(k*2+1,mid+1,r,x,y));
}
// 单点修改操作
void change(int pos,int l,int r,int x,int v){ //pos当前节点,l,r当前节点维护的区间,x为原序列中的值,v为要改成的值
if(r<x||l>x) return;
if(l==r&&l==x){
min[k]=v;
return;
}
int mid=(l+r)>>1;
change(pos*2+1,mid+1,r,x,v);
change(pos*2,l,mid,x,v);
min[pos]=min(min[k*2],min[k*2+1]);
}
套用模板查询范围内
#include<cstdio>
#include<algorithm>
#define maxn 100000
#define ll long long
using namespace std;
inline int read(){ //读入优化
int res=0,f=1; //res顾名思义结果 ,f用来判断正负
char ch;
while(ch<'0'||ch>'9'){ //读入前面的空格以及负号
if(ch=='-') f=-1; //如果是负号则f变-1
ch=getchar(); //连续读入,在最后读入负号之后也能再读入一位数字为下面的语句做帮助
}
while(ch>='0'&&ch<='9'){ //连续读入数字
res=res*10+ch-'0'; //更新答案
ch=getchar();
}
return res*f; //返回
}
int n,m;
ll sum[maxn*4];
void change(int k,int l,int r,int p,int v){ //修改pos=k的点,l,r为这个点的左右区间,p为这个点在原序列中的位置,v代表要修改成的值
if(r<p||l>p) return;
if(l==r&&p==l){
sum[k]+=v;
return;
}
int mid=(l+r)>>1;
change(k*2,l,mid,p,v);
change(k*2+1,mid+1,r,p,v);
sum[k]=sum[k*2]+sum[k*2+1];
}
long long query(int k,int l,int r,int x,int y){ //查询k节点,这个节点的左边是l,右边是r,查询区间x-y
if(y<l||x>r) return 0;
if(l>=x&&r<=y) return sum[k];
int mid=(l+r)>>1;
long long res=0;
res = query(k*2,l,mid,x,y);
res += query(k*2+1,mid+1,r,x,y);
return res;
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
int k=read(),l=read(),r=read(); //k==1表示求区间和,k==0 表示a[l]+r(a[l])是虚拟的目前来看
if(k==0) change(k,k,n,l,r);
else printf("%lld\n",query(1,l,n,l,r));
}
}
Lazy标记回头再说吧。。目前太菜没有深度理解的机会
标记永久化
。。。今天不想写了,去luogu练基础,明天再说吧。
树状数组
用途:快速获取区间和
基本思想:
根据任意的正整数关于2的不重复次幂的唯一分解性质,通过二进制中的最后一位来决定保存前缀的长度,将其分解成为多个子区间,分而治之
通过另外的数组 c[] 来保存已经运算好的值
功能:
1 查找前缀和
2 进行加法操作(看题)
3 更新操作
代码(单点修改):
#include<iostream>
#define maxn 10000
#define ll long long // 学到新姿势
using namespace std;
int a[maxn],c[maxn],n;
int lowbit(int x){ //返回x的最小次幂
return x&(-x); //背会就好了--返回的是x可被2分解的最小数
}
void add(int pos,int y){ //在pos上增加y
for(int i=pos;i<=n;i+=lowbit(i)) c[i]+=y; // 一定要 += lowbitx()
}
ll sum(int pos){ // 求pos上的前缀sum
ll sum=0;
for(int i=pos;i>0;i-=lowbit(i)) // 一个
sum+=c[i];
return sum;
}
int main(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
add(i,a[i]);
}
cout << sum(n);
return 0;
}
区间修改:
不会。。。。。。太菜了
并查集
用途:如名字一般,只有并和查的功能!!
强(缸 手动滑稽)烈推荐:https://blog.csdn.net/u013546077/article/details/64509038
原理:
找爸爸法:
开一个数组把爸爸们记录下来
优化方法:
在每一次插入之前,如果连接的不是根,则插入到根上
代码:
#include<iostream>
#define maxn 1e6
using namespace std;
int pre[(int)maxn],n; //pre存的是某位的爸爸
int find(int x){ //找爸爸
while(pre[x]!=x) x=pre[x];
return x;
}
void join(int x,int y){ //让x的爸爸认y的爸爸做儿子
int fx=find(x),fy=find(y);
if(fx!=fy);
pre[fx]=fy;
}
int main(){
cin >> n;
int tp,rpm;
for(int i=1;i<=n;i++) {
cin >> tp >> rpm;
pre[tp]=rpm;
}
}
RMQ问题
RMQ问题是指求区间最值的问题,RMQ (Range Minimum/Maximum Query)
ST表法:
ST算法是一种更加高效的算法,基于动态规划的思想,以O(nlogn)的预处理代价,换取O(1)的查询时性能。
动态规划方程:f[i , j] = max(f[i , j-1],f[ i + 2^(j-1), j-1])
f[i, 0] = a[i]
代码:
#include<iostream>
#include<cmath>
#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+(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;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
pre_st(); //制表
cin >> m;
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;
}
倍增法:
蒟蒻的我还不会,10月4日讲了再说
LCA:
全称:Least Common Ancestors 最近公共祖先
思路1:
给定[L,R]通过dfs,记录遍历结果到R为值,找到从[L,R]中深度最低的点就是最近公共祖先,使用ST表完成O(1)时间内的查询
代码:
尴尬了。。。忘了怎么存图了,10月4日讲完图的存储之后再说!(嘻嘻)