线段树从入门到跳楼
Posted shikeyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树从入门到跳楼相关的知识,希望对你有一定的参考价值。
首先先让我们认识离散化用的函数,STL给我们提供了便利:
unique(start,end);//取出有序序列重复元素,左闭右开,返回去重复序列最后一个元素位置
lower_bound(start,end,key);//左闭右开中寻找第一个大等于key的数,返回值
std::sort(a+1,a+n+1);
cnt = std::unique(a+1,a+n+1) - (a+1)//去重并获得去重后长度cnt,注意减的是初始地址qwq我错了一次
for(int i=1;i<=n;i++)
a[i] = lower_bound(a+1,a+1+cnt,a[i]) - a
我们需要对原数组进行一次备份(cp
数组),以便后面的复原
p3755 老c的任务
题解:
树状数组
将点按(x)递增;将询问拆成2个,离线,离散化,按(x)递增排序。按x轴排序,y轴维护树状数组。
然后每次找到一个询问,就判断有哪些点可以加到树状数组中,然后查询一下就好了。把区间询问转化为前缀和相减.
注意题目中边上也算是包含,所以应将左下边界-1;同时坐标必须是正的,还需要+2。
P4054计数问题
题解:多维树状数组板子
可以发现 , 矩阵中值的值域很小
(wle 100)考虑暴力思路,
构建(100)个二维树状数组,分别存储各数值 ,在矩阵中出现的次数.
单点修改:将a修改成b,先将a对应的树状数组改掉,对应位置出现次数-1,再对b对应的树状数组对应位置+1
区间查询:直接查询对应子矩阵出现次数即可
#include<cstdio>
#define maxn 301
#define lowbit(x) -x&x
#define ll long long
using namespace std;
inline int read()
{
char ch=getchar();int i=0,f=1;
while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();}
while(ch>=‘0‘&&ch<=‘9‘){i=(i<<1)+(i<<3)+ch-‘0‘;ch=getchar();}
return i*f;
}
int n,m,q;
int map[maxn][maxn],tree[101][maxn][maxn];
inline void add(int val,int x,int y,int z){//修改操作,将此点权值设为val,并使其次数+=z
map[x][y] = val;
for(int i = x;i <= n;i += lowbit(i))
for(int j = y;j <= m;j += lowbit(j))
tree[val][i][j] += z;
}
inline ll sum(int type,int x,int y){//查询1,1到x,y的type出现次数
int cnt = 0;
for(int i = x;i;i -= lowbit(i))
for(int j = y;j;j -= lowbit(j))
cnt += tree[type][i][j];
return cnt;
}
int main(){
n=read(),m=read();
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
add(read(),i,j,1);
q=read();
for(int i=1;i<=q;i++)
{
int opt = read();
if(opt == 1){
int x= read(),y=read(),tem=read();
add(map[x][y],x,y,-1);//原值出现次数-1
add(tem,x,y,1);//新值出现次数+1
}
else{
int x1=read(),x2=read(),y1=read(),y2=read(),type=read();
ll ans1 = sum(type,x2,y2),ans2=sum(type,x1-1,y1-1);
ll ans = ans2 + ans1 - sum(type,x1-1,y2) - sum(type,x2,y1-1);//容斥
//sum是不含边界的
printf("%lld
",ans);
}
}
}
线段树:满足区间可加性(f(x,y)=f(f(x,z),f(z,y)),zin[x,y])
线段树以常数大,空间大,难写难调而臭名远扬,但有时也很给力,当然能不写线段树就不写啦,而且记得要开四倍空间
以下部分参考LightHouseOfficial的内容
区间和,区间最大最小值,区间LCA,区间质数个数......可以用线段树维护
假设数组是{1,2,3,4,5,6}查询区间[2,4],计算出[2,4]的mid值,mid=(l+r)<<1=3
然后我们查询[l,mid]和[mid+1,r]区间[2,3]和[4,4]
[4,4]是叶子,返回它的值4,回到[2,3]继续递归
p<<1是左儿子,p<<1|1为右儿子
建树:
#define l(x) tree[x].l
#define r(x) tree[x].r
#define val(x) tree[x].val
void pushup(int p){
val(p)=val(p<<1)+val(p<<1|1);
}
void build(int p,int l,int r){
l(p)=l,r(p)=r;//保存每个节点的左右儿子的编号
if(l==r){
val(p)=a[l];
return;
}pushdown(p);
int mid=(l+r)>>1;
build(p<<1,l,mid);build(p<<1|1,mid+1,r);//递归建树
pushup(p);//更新值
}
int query(int p,int l,int r){//查询区间和 p是fa
if(l<=tree[p].l && r>=tree[p].r)return tree[p].val;
pushdown(p);
int mid=(tree[p].l+tree[p].r)>>1;
int ret=0;
if(l<=mid) ret+=query(p<<1,l,r);
if(r>mid) ret+=query(p<<1|1,l,r);
return ret;
}
区间修改,打lazytag 然后pushdown
#define val(x) tree[x].val
#define add(x) tree[x].add
void pushdown(int p){
if(add(p)){
val(p<<1)+=add(p)*(r(p<<1)-l(p<<1)+1);
val(p<<1|1)+=add(p)*(r(p<<1|1)-l(p<<1|1)+1);
add(p<<1)+=add(p);
add(p<<1|1)+=add(p);
add(p)=0;
}
}//加法
void update(int p,int l,int r,int d){//修改
if(l<=l(p) && r>=r(p)){
val(p)+=d*(r(p)-l(p)+1);add(p)+=d;
return;
}
pushdown(p);
int mid=(l(p)+r(p))>>1;
if(l<=mid)update(p<<1,l,r,d);
if(r>mid)update(p<<1|1,l,r,d);
pushup(p);
}
动态开点线段树
由于线段树要maxn<<2,maxn过大显然MLE,这时就要离散化或动态开点了
意思是,你要用到一个点才开那个点,不用的点不开,可以大幅节省空间,空间复杂度降为O(nlogn)
(log_210^6approx20)是不是很给力
接下来是实现:
一开始,你只有一个根节点。
通过update函数往树里面插点,开两个数组记录每个节点的左右儿子编号。
递归进入左右儿子,如果要用新点,就开新点。
可持久化线段树
对于每个询问我们每次不开一整棵树,而是每次把修改节点插入,也就是插入一些历史节点来储存历史值。
空间复杂度成了(nlog^2n),一般开32倍空间<<5
一般可持久化线段树的题目只要求单点修改,区间修改可以实现不过复杂度比较高。
真的遇到了需要写主席树而且要求区间修改的题目,一般可以转化为单点修改进行操作
时间复杂度((n+m)logn)
还没完咕咕咕
以上是关于线段树从入门到跳楼的主要内容,如果未能解决你的问题,请参考以下文章