zoukankan      html  css  js  c++  java
  • 基础线段树

    模板

    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...)

    ``

  • 相关阅读:
    CSS简单的四种引入方式
    html之表单标签
    html基础标签之head和body标签
    Python之协程的实现
    Python之实现不同版本线程池
    Python多进程之multiprocessing模块和进程池的实现
    Python之queue模块
    sqlserver 时间格式化
    关于sql server 代理(已禁用代理xp)解决办法
    sqlserver如何启动数据库邮件
  • 原文地址:https://www.cnblogs.com/xcxc82/p/13445851.html
Copyright © 2011-2022 走看看