[知识点]线段树标记永久化

前言:

本文由Hallmeow原创,转载请注明出处!

由于打丧心病狂的 [BZOJ 4826]影魔  导致需要学习标记永久化,于是入坑OvO

知识点:线段树标记永久化

对于树套树,主席树等使用到线段树的比较复杂的数据结构,如果我们区间修改的话,打标记后pushdown或者pushup是很费劲的

那么我们能不能不用pushdown和pushup呢?当然可以啦!这样就用到标记永久化了!

原理就是: 在路过该节点的时候把修改对答案的影响加上,来省去标记下放的过程

实现起来:

线段树的每个节点维护 sum 与 add 两个标记

修改时:

设区间[xl,xr]全部加v

当目前询问区间与当前区间完全重合的时候,更新add的值,返回。

在一路下来的时候把所有经过的区间(相当于包含询问区间的区间)的sum加上此次修改所产生的影响 v*(xr-xl+1)。

注意完全重合之后就返回了,也就是说下面的部分的影响还没有更新。不要着急

void update(int rt,int l,int r,int v,int xl,int xr){
	sum[rt]+=v*(xr-xl+1);
	if(l==xl&&r==xr){
		add[rt]+=v; return;
	}
	int mid=(l+r)>>1;
	if(xr<=mid)	update(rt<<1,l,mid,v,xl,xr);
	else{
		if(xl>mid)	update(rt<<1|1,mid+1,r,v,xl,xr);
		else update(rt<<1,l,mid,v,xl,mid),update(rt<<1|1,mid+1,r,v,mid+1,xr);
	}
}

询问时:

由于上面的更新没有对下面产生影响,所以我们需要一路累加add,直到目前询问区间与当前区间完全重合的时候,答案为sum+add*区间长度

注意累加add不用累加上完全重合的区间的add,因为它已经在修改的时候对sum进行更新了

int query(int rt,int ad,int l,int r,int xl,int xr){
	if(xl==l&&xr==r){
		return sum[rt]+ad*(xr-xl+1);
	}
	int mid=(l+r)>>1;
	if(xr<=mid) return query(rt<<1,ad+add[rt],l,mid,xl,xr);
	else{
		if(xl>mid) return query(rt<<1|1,ad+add[rt],mid+1,r,xl,xr);
		else return query(rt<<1,ad+add[rt],l,mid,xl,mid)+query(rt<<1|1,ad+add[rt],mid+1,r,mid+1,xr);
	}
}

区间修改线段树标记永久化模板

#include<iostream>
#include<cstdio>
#include<cstring>
#define pos(i,a,b) for(int i=(a);i<=(b);i++)
#define N 201000
using namespace std;
int n,m;
int sum[N*4],add[N*4];
int a[N];
void build(int l,int r,int rt){
	if(l==r){
		sum[rt]=a[l];return;
	}
	int mid=(l+r)>>1;
	build(l,mid,rt<<1);
	build(mid+1,r,rt<<1|1);
	sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void update(int rt,int l,int r,int v,int xl,int xr){
	sum[rt]+=v*(xr-xl+1);
	if(l==xl&&r==xr){
		add[rt]+=v; return;
	}
	int mid=(l+r)>>1;
	if(xr<=mid)	update(rt<<1,l,mid,v,xl,xr);
	else{
		if(xl>mid)	update(rt<<1|1,mid+1,r,v,xl,xr);
		else update(rt<<1,l,mid,v,xl,mid),update(rt<<1|1,mid+1,r,v,mid+1,xr);
	}
}
int query(int rt,int ad,int l,int r,int xl,int xr){
	if(xl==l&&xr==r){
		return sum[rt]+ad*(xr-xl+1);
	}
	int mid=(l+r)>>1;
	if(xr<=mid) return query(rt<<1,ad+add[rt],l,mid,xl,xr);
	else{
		if(xl>mid) return query(rt<<1|1,ad+add[rt],mid+1,r,xl,xr);
		else return query(rt<<1,ad+add[rt],l,mid,xl,mid)+query(rt<<1|1,ad+add[rt],mid+1,r,mid+1,xr);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	pos(i,1,n) scanf("%d",&a[i]);
	build(1,n,1);
	pos(i,1,m){
		int opt;scanf("%d",&opt);
		int x,y;scanf("%d%d",&x,&y);
		if(opt==1){
			int k;scanf("%d",&k);
			update(1,1,n,k,x,y);
		}
		else printf("%d\n",query(1,0,1,n,x,y));
	}
	return 0;
}
时间: 2024-10-28 22:12:40

[知识点]线段树标记永久化的相关文章

POJ 2155 Matrix 二维线段树+标记永久化?

题意:链接 方法:二维线段树+标记永久化 解析:题意是比较明朗的,算一下内存发现4000^2也是可以接受的,于是就开始yy二维线段树? 对于第一层x的线段树,每个节点开一整棵y线段树. 用个二维数组就实现了? 不过发现个问题啊,这题怎么pushdown啊,标记传不下去啊.如果给x打个标记那么怎么知道y传那段啊? 于是就学了新的东西?标记永久化. 本题是单点查询嘛,标记永久化就应该是把相应的区间直接异或,不用往下传?那查询的时候怎么办呢?只需要在查询的时候把所有路过该点的区间都异或起来就OK了.貌

线段树标记永久化

前言 对于树套树,主席树等使用到线段树的比较复杂的数据结构,如果区间修改的话,打标记后pushdown或者pushup是很难做到的完全不行吧 所以这个时候,一个神奇的东西诞生了... 正题 线段树标记永久化,维护一个标记,假设为cov,再维护一个sum 假设修改区间[ql, qr]全部加上v: 和平常一样,到这个区间后cov[x] += v 但是我们又不想pushup,怎么办? 很好做,更新的时候每次sum[x] += v * (qr - ql + 1) (注意这里的qr,ql是完全被包含于线段

【BZOJ1513】【POI2006】Tet-Tetris 3D 二维线段树+标记永久化

题解:题意很裸啊~~~ 培训的时候说要写标记永久化,反正永久化很水,就直接写了. 但是我并不知道为什么要永久化,或者说理解不深刻,但是再遇上肯定能分析出来233. 大概应该可能或许就是: 直接原因:下传标记传不下去. 根本原因: 线段树有两层,这样它的传递可能就有点像拓扑了 就是外层线段树需要往内层线段树传,然后内层线段树还要下传 这样扫到某处时发现,****,还需要顺着两边的标记路径回溯到根, 然后各种压栈啊什么的,才能传下去,而且大概率出错(代码错或思路错). 所以简简单单写个标记永久化好了

HFUUOJ1024 动态开点线段树+标记永久化

题意 分析 动态加点线段树,标记永久化好写常数小 Code #include<bits/stdc++.h> #define fi first #define se second #define lson l,mid,p<<1 #define rson mid+1,r,p<<1|1 #define pb push_back #define ll long long using namespace std; const ll inf=1e18; const int mod=

知识点 - 线段树 权值 树套树 二维 可持续

知识点 - 线段树 权值 树套树 二维 可持续 //区间更新求和 inline int ls(int p) { return p << 1; }//左儿子 inline int rs(int p) { return p << 1 | 1; }//右儿子 void push_up(int p) { t[p] = t[ls(p)] + t[rs(p)]; }// 向上不断维护区间操作 void build(ll p, ll l, ll r) { if (l == r) { t[p] =

[HDU4348]To the moon(主席树+标记永久化)

学可持久化treap的时候才发现自己竟然没写过需要标记下传的主席树,然而现在发现大部分操作都可以标记永久化,下传会增大占用空间. 这题一种写法是和普通的线段树一样标记下传,注意所有修改操作(包括put())都要新建点.于是MLE了. 1 #include<cstdio> 2 #include<algorithm> 3 #define lson v[x].ls,L,mid 4 #define rson v[x].rs,mid+1,R 5 #define rep(i,l,r) for

[知识点]线段树

// 此博文为迁移而来,写于2015年3月30日,不代表本人现在的观点与看法.原始地址:http://blog.sina.com.cn/s/blog_6022c4720102vw6j.html 1.前言 一道题目:给出一个一维数组共n个结点,每次对他进行一些操作:在[l,r]范围内增加x,或是询问第i个结点当前的值为多少.这不水题嘛!直接模拟就可以了.但是如果n<=100000呢?询问次数超过100000呢?O(n^2)并不能扛住.今天要引入的内容就是线段树.这东西很早之前有提到过,我也编过,但

[BZOJ 4826]影魔 区间修改主席树 标记永久化

为了这道题还特地去学了标记永久化,可能对于区间修改主席树或者树套树比较有用吧OvO 我们可以把答案分为两部分:p1造成的和p2造成的 我们枚举序列,用单调栈求出序列每一个位置i,左右边第一个比它大的L,R 开三棵主席树tree1 tree2 tree3 把L扔进tree1的R位置(单点+1),L+1~i-1扔进tree2的R位置,i+1~R-1扔进tree3的L位置(区间+1) 然后询问[l,r]的时候,求出三棵区间主席树 p1造成的贡献为区间tree1内大于等于L的个数 p2造成的贡献为区间t

zoj 3573 Under Attack(线段树 标记法 最大覆盖数)

Under Attack Time Limit:  10 Seconds      Memory Limit:  65536 KB Doctor serves at a military air force base. One day, the enemy launch a sudden attack and the base is under heavy fire. The fighters in the airport must take off to intercept enemy bom