zoukankan      html  css  js  c++  java
  • 线段树模板

    Dev.C++线段树模板:在对一段区间进行修改或查询时,数组(\(n^2\))……显而易见,会爆……这时,线段树(\(nlogn\))就是一个很好的选择
    老规矩,先贴代码:


    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    const ll N=100100;
    ll n,m,a[N];
    struct tree{
    	ll l,r;
    	ll pre,add;
    }t[4*N];
    void build_tree(ll p,ll l,ll r){
    	t[p].l=l,t[p].r=r;
    	if( l==r ) {
    		scanf("&lld",&t[p].pre);
    		return ;
    	}
    	ll mid=(l+r)/2;
    	build_tree(p*2,l,mid);
    	build_tree(p*2+1,mid+1,r);
    	t[p].pre=t[p*2].pre+t[p*2+1].pre;
    }
    void lazy_tag(ll p){
    	if( t[p].add ) {
    		t[p*2].pre+=t[p].add*(t[p*2].r-t[p*2].l+1);
    		t[p*2+1].pre+=t[p].add*(t[p*2+1].r-t[p*2+1].l+1);
    		t[p*2].add+=t[p].add;
    		t[p*2+1].add+=t[p].add;
    		t[p].add=0;
    	}
    }
    void update(ll p,ll x,ll y,ll z){
    	if( x<=t[p].l && y>=t[p].r ) {
    		t[p].pre+=(ll)z*(t[p].r-t[p].l+1);
    		t[p].add+=z;
    		return;
    	}
    	lazy_tag(p);
    	ll mid=(t[p].l+t[p].r)/2;
    	if( x<=mid ) update(p*2,x,y,z);
    	if( y>mid ) update(p*2+1,x,y,z);
    	t[p].pre=t[p*2].pre+t[p*2+1].pre;
    }
    ll query(ll p,ll x,ll y){
    	if( x<=t[p].l && y>=t[p].r ) return t[p].pre;
    	lazy_tag(p);
    	ll mid=(t[p].l+t[p].r)/2;
    	ll ans=0;
    	if( x<=mid ) ans+=query(p*2,x,y);
    	if( y>mid ) ans+=query(p*2+1,x,y);
    	return ans;
    }
    int main(){
    	scanf("%lld%lld",&n,&m);
    	build_tree(1,1,n);
    	for(ll i=1;i<=m;i++){
    		ll p,x,y,k;
    		scanf("%lld",&p);
    		if( p==1 ){
    			scanf("%lld%lld%lld",&x,&y,&k);
    			update(1,x,y,k);
    		} 
    		if( p==2 ){
    			scanf("%lld%lld",&x,&y);
    			printf("%lld\n",query(1,x,y));
    		} 
    	}
    	return 0;
    }
    

    意义:在对一段区间进行修改或查询时,数组(\(n^2\))……显而易见,会爆……这时,线段树(\(nlogn\))就是一个很好的选择
    使用:建立一棵二叉树,用数组存储这棵树,叶子节点就是你要存的(空间换时间)。
    递归存储:


    void build_tree(ll p,ll l,ll r){
    	t[p].l=l,t[p].r=r;
    	if( l==r ) {
    		scanf("&lld",&t[p].pre);
    		return ;
    	}
    	ll mid=(l+r)/2;
    	build_tree(p*2,l,mid);
    	build_tree(p*2+1,mid+1,r);
    	t[p].pre=t[p*2].pre+t[p*2+1].pre;
    }
    

    递归建树变量:p(数组下标),l(左端点),r(右端点)。
    if( l==r ) :在它搜到叶子(左端点等于右端点)时,输入叶子数据(建树)
    ll mid=(l+r)/2:二分建树
    t[p].pre=t[p2].pre+t[p2+1].pre:求 祖宗 父节点
    这个递归建树还是比较容易理解的哈简单到爆
    先别飘哈咱还有难的呢

    void lazy_tag(ll p){
    	if( t[p].add ) {
    		t[p*2].pre+=t[p].add*(t[p*2].r-t[p*2].l+1);
    		t[p*2+1].pre+=t[p].add*(t[p*2+1].r-t[p*2+1].l+1);
    		t[p*2].add+=t[p].add;
    		t[p*2+1].add+=t[p].add;
    		t[p].add=0;
    	}
    }
    void update(ll p,ll x,ll y,ll z){
    	if( x<=t[p].l && y>=t[p].r ) {
    		t[p].pre+=(ll)z*(t[p].r-t[p].l+1);
    		t[p].add+=z;
    		return;
    	}
    	lazy_tag(p);
    	ll mid=(t[p].l+t[p].r)/2;
    	if( x<=mid ) update(p*2,x,y,z);
    	if( y>mid ) update(p*2+1,x,y,z);
    	t[p].pre=t[p*2].pre+t[p*2+1].pre;
    }
    ll query(ll p,ll x,ll y){
    	if( x<=t[p].l && y>=t[p].r ) return t[p].pre;
    	lazy_tag(p);
    	ll mid=(t[p].l+t[p].r)/2;
    	ll ans=0;
    	if( x<=mid ) ans+=query(p*2,x,y);
    	if( y>mid ) ans+=query(p*2+1,x,y);
    	return ans;
    }
    

    这这这……这个查询和更改就一起讲了,思路是差不多的,就是有一个懒惰标记需要理解理解,其实学学就会了
    得了,不吹牛了,讲代码。看图

    lazy_tag
    蓝色部分(除了10__10,那是标错了)是要查询或修改的祖先,红色部分是查询或修改的部分。如果没有lazy_tag,他的速度会大幅下降!!!
    这是我们要查询or修改2~9的区间。
    未使用lazy_tag,则被红色区域框起来(既4_4 5_5 6_7 8_8 6_6 7_7)的线段皆要更改,查询则是要查到最深,浪费时间。lazy_tag,就是用来降时间的。
    它只需要查到或修改属于这段区间的区间。例如
    2~9有1_3 4_5 6_8 9_10。其中13其中包括了23,所以1_3也要继续往下查。4、6都大于2 , 5、8都小于9,这里就做一个标记,表示下面要进行更改or查询
    9_10包括了9这个节点,却不在2~9中,所以要继续往下查。最后进行标记的有2_2 3_3 4_5 6_8 9_9。要注意,如果破坏这个作了标记的节点,tag要向下传递。
    例如我要修改2_9,我给4_5和6_8加持了tag。而我要查询5~6,我就要把4_5和6_8继续查下去,那么4_5和6_8的tag就会破坏掉,而他的儿子就会加上一个tag。这就是lazy_tag。

    他很好理解对不对?它的代码复杂度有点……低,但是理解它却不简单。


    结论:线段树是一个很棒的数据结构,相对的,它的代码复杂度很高(要打一堆函数)。说白了它就像高精度一样是个模板,(不知道高精度的[戳这](https://www.cnblogs.com/riced/p/13393466.html))。

    所以最好背下来哦!

  • 相关阅读:
    未能加载文件或程序集“Oracle.DataAccess”或它的某一个依赖项。试图加载格式不正确的程序
    Silverlight Visifire控件 后台设置颜色
    silverlight 生成图表 WCF 解析XML代码.svc.cs 文件
    silverlight 生产图表(动态图表类型,Y轴数量) .xaml.cs文件
    Silverlight Visifire控件应用去水印
    .net简单的aspx创建
    Silverlight Visifire控件 .net后台控制aspx页面控件的显示与隐藏,动态给控件赋值,选定默认值的设定
    .net后台 Silverlight 页面 动态设置 ASPX 页面 控件的Margin值(位置设置)
    转载 Silverlight实用窍门系列:1.Silverlight读取外部XML加载配置---(使用WebClient读取XAP包同目录下的XML文件))
    java 读取Excel文件并数据持久化方法Demo
  • 原文地址:https://www.cnblogs.com/riced/p/13394040.html
Copyright © 2011-2022 走看看