基础线段树

模板

P3373 【模板】线段树 2

(mtag)为乘法标记,(atag)为加法标记

对于下放后的每一个区间来说,(x=x*mtag+atag*len)(式(1))

(x=xcdot mtag_2+lencdot atag_2=(xcdot mtag_2+lencdot atag_2)cdot mtag_1+atag_1cdot len)

(=xcdot mtag_2cdot mtag_1+len*atag_2cdot mtag_1+atag_1cdot len)

(=xcdot (mtag_2cdot mtag_1)+lencdot (atag_2cdot mtag_1+atag_1))

再根据前面的式(1),易得

(mtag_2 = mtag_1cdot mtag_2)

(atag_2=atag_2cdot mtag_1+atag_2)

核心(下放)代码:

void pushdown(tree2 *tree,int l,int r){
	if(tree->lazym==1&&tree->lazyp==0||tree->lson==NULL) return;
	int mid = (l+r)>>1;
	tree->lson->x = (tree->lson->x*tree->lazym+(long long)tree->lazyp*(mid-l+1))%mo;
	tree->rson->x = (tree->rson->x*tree->lazym+(long long)tree->lazyp*(r-mid))%mo;
	tree->lson->lazym = (tree->lazym*tree->lson->lazym)%mo;
	tree->rson->lazym = (tree->lazym*tree->rson->lazym)%mo;
	tree->lson->lazyp = (tree->lazym*tree->lson->lazyp+tree->lazyp)%mo;
	tree->rson->lazyp = (tree->lazym*tree->rson->lazyp+tree->lazyp)%mo;
	tree->lazym  = 1;
	tree->lazyp = 0;
}

基础练习题

P4145 上帝造题的七分钟2 / 花神游历各国

P6327 区间加区间sin和

P1438 无聊的数列

P4513 小白逛公园

P4588 [TJOI2018]数学计算

P2894 [USACO08FEB]Hotel G

P4145 上帝造题的七分钟2 / 花神游历各国

照题里的这个数据范围来看,直接暴力开方肯定会T飞

通过观察,不难发现数据范围内最大的数也只需要(6)次开方就可以变为(1)

考虑剪枝优化:

当一个区间的最大值为(1)时,其整个区间的其他值肯定也为(1)

因此当区间内最大值等于(1)时,直接(return)

核心代码:

void change(int node,int l,int r){
	int L = tree[node].l,R = tree[node].r;
	if(L==R){
		tree[node].sum = sqrt(tree[node].sum);
		tree[node].maxn = sqrt(tree[node].maxn);
		return;
	}
	int mid = (L+R)>>1;
	if(l<=mid&&tree[lson].maxn>1){//最大值大于1时在进行修改操作
			change(lson,l,r);
	}
	if(r>mid&&tree[rson].maxn>1){
			change(rson,l,r);
	}
	pushup(node);
}

P6327 区间加区间sin和

挺好的一道题目,很适合线段树初学者练手

学过和差角公式的应该都能很快想出解法

(sin(a+x) = sinacosx+cosasinx)

(cos(a+x) = cosacosx-sinxsina)

只需要在线段树里维护一个(sinx)和一个(cosx)即可

核心代码:

void update2(int node,double sinv,double cosv){//和差角公式
	double cosa = tree[node].cosx;
        double sina = tree[node].sinx;
	tree[node].cosx = cosa*cosv-sina*sinv;
	tree[node].sinx = sina*cosv+cosa*sinv;
	return;
}
void pushdown(int node){//下放
	if(tree[node].tag){
		double cosa = cos(tree[node].tag),sina = sin(tree[node].tag);
		update2(lson,sina,cosa);//更新儿子的值
		update2(rson,sina,cosa);
		tree[lson].tag+=tree[node].tag;//更新儿子的tag
		tree[rson].tag+=tree[node].tag;
		tree[node].tag = 0;b
	}
	return;
}

void change(int node,int l,int r,int x){//更新操作
	int L = tree[node].l,R = tree[node].r;
	if(R<=r&&L>=l){//包围在区间内,直接修改
		tree[node].tag+=x;
		update2(node,sin(x),cos(x));
		return;
	}
	pushdown(node);//下放
	int mid = (L + R)>>1;
	if(l<=mid) change(lson,l,r,x);
	if(r>mid) change(rson,l,r,x);
	update(node);//上传更新
	return;
}

P1438 无聊的数列

利用线段树来维护差分数组。

每当进行一个操作(1)

将点(l)加上首相(k)

如果区间不是一个点的话,则将区间([l+1,r])上的点都加上公差(d)

如果(r<n),则在(r+1)的位置上加上(-(k+(r-l)cdot d))),便于差分

查询时,将区间([1,k])的值都加上即可,相当于查询操作

区间查询,区间修改,直接上线段树模板即可

核心代码:

for(int i=1;i<=m;i++){
		int mode,l,r,k,d;
		scanf("%d",&mode);
		if(mode==1){
			scanf("%d%d%d%d",&l,&r,&k,&d);
			change(1,1,n,l,l,k);//修改左端点
			if(l!=r) change(1,1,n,l+1,r,d);//修改区间
			if(r+1<=n) change(1,1,n,r+1,r+1,-(k+(r-l)*d));//修改右端点
		}
		else{
			scanf("%d",&d);
			printf("%d
",query(1,1,n,1,d)+a[d]);//差分数组的值+原值
		}
	}

P4513 小白逛公园

线段树经典题

维护一个从区间左端点开始的区间最大子段(maxl),从右端点开始的区间最大子段(maxr),总区间最大子段(maxx),和一个区间和(sum)

对于(maxl)来说,其右端点的位置有两种可能:

  • 在左儿子中

  • 在右儿子中

得到方程:(tree.maxl = max(lson.maxl,lson.sum+rson.maxl))

(maxr)也同理

方程:(tree.maxr = max(rson.maxr,rson.sum+lson.maxr))

对于(maxx)来说,其区间范围有三种可能

  • 只在左儿子中

  • 只在右儿子中

  • 既在左儿子中也在右儿子中

得到方程:(tree.maxx = max(lson.maxx,rson.maxx,lson.maxr+rson.maxl)))

查询时只需输出区间([l,r])中的(maxx)即可

核心代码:

更新操作

void update(tree2 *tree,tree2 *lson,tree2 *rson){
	tree->sum = lson->sum+rson->sum;
	tree->maxl = max(lson->maxl,lson->sum+rson->maxl);
	tree->maxr = max(rson->maxr,rson->sum+lson->maxr);
	tree->maxX = max(lson->maxX,max(rson->maxX,lson->maxr+rson->maxl));
}

查询操作

tree2 *query(tree2 *tree,int l,int r,int x,int y){
	if(x<=l&&y>=r)
	return tree;
	int mid = (l + r)>>1;
	tree2 *t1 = NULL,*t2 = NULL;
	if(x<=mid) t1 = query(tree->lson,l,mid,x,y);
	if(y>mid) t2 = query(tree->rson,mid+1,r,x,y);
	if(t1==NULL) return t2;
	if(t2==NULL) return t1;
	tree2 *ret = &dizhi[++t];
	update(ret,t1,t2);
	return ret;
}

P4588 [TJOI2018]数学计算

比较简单的一道题目。

仔细观察不难发现

所谓的操作(1)跟操作(2)其实就是在进行普通的单点修改操作而已

用一个线段树在记录一段区间内的总乘积

操作(1)是把点(i)的值从(1)修改为(i)

操作(2)则是把点(pos)的值修改为(1)

核心代码:

	void pushup(int node){
	tree[node].val = (tree[lson].val*tree[rson].val)%mo;
}
void build(int node,int l,int r){
	if(l==r){
		tree[node].val = 1;
		return;
	       }
	long long mid = (l + r)>>1;
	build(lson,l,mid);
	build(rson,mid+1,r);
	pushup(node);
}
void change(int node,int l,int r,int x,int y){
	if(l==r){
		tree[node].val = y;
		return;
	}
		int mid = (l + r)>>1;
		if(x<=mid) change(lson,l,mid,x,y);
		else change(rson,mid+1,r,x,y);
		pushup(node);
         }

P2894 [USACO08FEB]Hotel G

P4513 小白逛公园大同小异的思路

只是把单点修改操作换成了区间修改罢了

要注意的是这里不存在负值的情况

因此在上传操作时转移没那么复杂,只用判断(maxl)是否等于(sum)

若等于,说明左儿子中房间全为空,直接全部加上,再加上右儿子的(maxl)

若不等于,则为左儿子的(maxl)

(maxr)也同理

同时也要注意这里的查询查的是满足长度为(x)的最左的端点

因此在查询时要满足"能左则左"

核心代码:

void pushup(int node){//上传
	if(tree[lson].maxx==tree[lson].sum){//如果左区间全为空房
		tree[node].lmax = tree[lson].sum+tree[rson].lmax;//全部加上
	}
	else{
		tree[node].lmax = tree[lson].lmax;
	}
	if(tree[rson].maxx==tree[rson].sum){
		tree[node].rmax = tree[rson].sum+tree[lson].rmax;
	}
	else{
		tree[node].rmax = tree[rson].rmax;
	}
	tree[node].maxx = max(tree[lson].rmax+tree[rson].lmax,max(tree[lson].maxx,tree[rson].maxx));
}
void build(int node,int l,int r){
	
	tree[node].sum = tree[node].lmax = tree[node].rmax = tree[node].maxx =r-l+1;
	if(l==r){
		return;
	}
	build(lson,l,mid);
	build(rson,mid+1,r);
}
void pushdown(int node){//下放
	if(tree[node].lazy==0) return;
	if(tree[node].lazy==1){//退房
		tree[lson].maxx = tree[lson].rmax = tree[lson].lmax = 0;
		tree[rson].maxx = tree[rson].rmax = tree[rson].lmax = 0;
		tree[lson].lazy = tree[rson].lazy = 1;
	}
	if(tree[node].lazy==2){//开房
		tree[lson].maxx = tree[lson].rmax = tree[lson].lmax = tree[lson].sum;
		tree[rson].maxx = tree[rson].rmax = tree[rson].lmax = tree[rson].sum;
		tree[lson].lazy = tree[rson].lazy = 2;	
	}
	tree[node].lazy = 0;
}
void change(int node,int l,int r,int x,int y,int opt){//opt为1代表退房,为2代表开房
	
	
	if(x<=l&&y>=r){
		if(opt==1) tree[node].maxx = tree[node].lmax = tree[node].rmax = 0;
		
		else tree[node].maxx = tree[node].lmax = tree[node].rmax = tree[node].sum;
		
		tree[node].lazy = opt;
		return;
	}
	pushdown(node);
	if(x<=mid) change(lson,l,mid,x,y,opt);
	if(y>mid) change(rson,mid+1,r,x,y,opt);
	pushup(node);
}
int query(int node,int l,int r,int x){//查询
	pushdown(node);
	if(l==r) return l;
	if(tree[lson].maxx>=x){//如果左区间的最大值大于x,直接查左边
		return query(lson,l,mid,x);
	}
	else if(tree[lson].rmax+tree[rson].lmax>=x){//如果中间大于x
		return 1+mid-tree[lson].rmax;左儿子的右最大值,也就是最靠近左边的端点
	} 
	else return query(rson,mid+1,r,x);//否则查右边
}
}

动态开点

P5459 [BJOI2016]回转寿司

CF915E Physical Education Lessons

(now ~ loading...)

扫描线

P5490 【模板】扫描线

P1502 窗口的星星

(now ~ loading...)

``

原文地址:https://www.cnblogs.com/xcxc82/p/13445851.html