线段树从入门到跳楼

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)
还没完咕咕咕



以上是关于线段树从入门到跳楼的主要内容,如果未能解决你的问题,请参考以下文章

树链剖分入门

树链剖分

扫描线——~~原理分析与代码块(指针线段树)~~——从入门到放弃

树链剖分

线段树合并:从入门到放弃

Atom编辑器入门到精通 Atom使用进阶