zoukankan      html  css  js  c++  java
  • 线段树——hyl天梦

    1简介

    线段树是一种比较实用的数据结构,支持区间修改,区间查询,单点修改,单点查询,维护区间最值。

    2讲解

    2.1基本值

    (注:本博客只是设计线段树区间求和)
    在结构体中我们维护一个区间值,区间长度,和该节点的标记

    这个标记非常重要,是线段树的灵魂

    代码:

    	struct node{
    		ll sum,len;
    		ll ad;
    	};
    

    2.2建树

    对于一个数组a来说,把它l到r的一个区间看做一个节点,根节点的区间是l到r,根节点两个儿子的区间分别为l到mid,和mid+1到r,其中mid=l+r>>1;由此不断往下递归,直至该区间内只剩一个节点。
    上述说明不仅说明了线段树的构造原理,也可以得出线段树的建树代码:

    inline void build(ll k,ll l,ll r)
    {
    	if(l==r)
    	{
    		tree[k].len=1;
    		tree[k].sum=a[l];
    		return;
    	}
    	ll mid=l+r>>1;
    	build(k*2,l,mid);
    	build(k*2+1,mid+1,r);
    	pushup(k);
    }
    

    其中pushup函数是合并函数,根据代码,我们可以知道在建树中,是首先建立叶子结点,然后逐步回溯,合并,建立父节点,那么就有了合并函数。

    2.3合并函数

    合并函数比较好理解,一个节点对应一个区间,那么这个区间l到r我们所要维护的值一定取决于它的两个儿子,例如,如果我们所维护的是l到r的和,那么该节点所维护的l到r的区间和一定是它的两个儿子:l到mid和mid+1到r的和,长度也是如此,由此可以得出:

    	inline void pushup(ll k)
    	{
    		tree[k].sum=tree[k*2].sum+tree[k*2+1].sum;
    		tree[k].len=tree[k*2].len+tree[k*2+1].len;
    	}
    

    2.4加法函数与标记下传

    如果要给一个区间和为q,区间长度为len的区间进行把区间里的每个数都加x的操作,那么就是相当于整个区间的和加上x*len:

    	inline void A(ll k,ll x)
    	{
    		tree[k].sum+=tree[k].len*x;
    		tree[k].ad+=x;
    	}
    

    但是我们并没有修改这个区间的每一个数,如果我们需要用到这个区间里的某些数但是又不是全部,这个时候因为我们个这个区间的标记加上了x,也就是说我们标记l到r的区间加上了x,接下来我们要做的就是让标记下传,把标记传给该节点的两个儿子,同时更新它的两个儿子所维护的区间和与标记,这样,我们需要用到什么程度就标记下传的什么程度,一边修改一边标记下传。
    这就体现出了为什么线段树的时间复杂度更优:我们需要修改l到r的区间和就只看l到r的区间和,而不用给l到r的每一个值都进行修改,如果有需要用到一些更小的区间,那么标记下传
    标记下传同时,不要忘了清零该节点标记:

    	inline void pushdown(ll k)
    	{
    		A(k*2,tree[k].ad);
    		A(k*2+1,tree[k].ad);
    		tree[k].ad=0;
    	}
    

    2.5区间更改

    如果让我们修改的区间已经查到了,就执行加法操作,如果没有,就看他的左右儿子,同时标记下传,最后不要忘记合并
    有时我们所要修改的区间包含在多个线段树节点中,我们分治来考虑,如果mid把我们的目标区间分成两段,那就分别来处理
    下面的代码中:z到y指的是目标区间,l到r指的是当前区间,k指的是当前区间所对应的线段树节点,x指的是要给区间修改的值

    	inline void change(ll k,ll l,ll r,ll z,ll y,ll x)
    	{
    		if(l==z&&r==y)
    		{
    			A(k,x);
    			return;
    		}
    		if(tree[k].ad) pushdown(k);
    		ll mid=l+r>>1;
    		if(y<=mid) change(k*2,l,mid,z,y,x);
    		else if(z>=mid+1) change(k*2+1,mid+1,r,z,y,x);
    		else change(k*2,l,mid,z,mid,x),change(k*2+1,mid+1,r,mid+1,y,x);
    		pushup(k);
    	}
    

    2.6区间查询

    区间查询的思路与2.5差不多,基本上还是一个分治的思想,同时别忘了标记下传:

    	inline ll ask_sum(ll k,ll l,ll r,ll z,ll y)
    	{
    		if(l==z&&r==y) return tree[k].sum;
    		if(tree[k].ad) pushdown(k);
    		ll mid=l+r>>1;
    		if(y<=mid) return ask_sum(k*2,l,mid,z,y);
    		else if(z>=mid+1) return ask_sum(k*2+1,mid+1,r,z,y);
    		else return ask_sum(k*2,l,mid,z,mid)+ask_sum(k*2+1,mid+1,r,mid+1,y);
    	}
    

    总结

    以上就是线段树大致内容,博主讲的说实话并不是很好,希望多多谅解,如果有不清楚的地方可以在评论区里提问。这里附上总代码,该代码可以过洛谷上的线段树模板1
    本博客只是介绍了区间加法,实际上区间加减乘除,最值,线段树都可以维护。

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #include<sstream>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    #include<deque>
    #include<cstdlib>
    #include<ctime>
    #define dd double
    #define ll long long
    #define ull unsigned long long
    #define N 1000100
    #define M number
    using namespace std;
    
    ll n,m,a[N];
    
    struct Stree{
    	
    	struct node{
    		ll sum,len;
    		ll ad;
    	};
    	
    	node tree[N<<4];
    
    	inline void A(ll k,ll x)
    	{
    		tree[k].sum+=tree[k].len*x;
    		tree[k].ad+=x;
    	}
    
    	inline void pushdown(ll k)
    	{
    		A(k*2,tree[k].ad);
    		A(k*2+1,tree[k].ad);
    		tree[k].ad=0;
    	}
    	
    	inline void pushup(ll k)
    	{
    		tree[k].sum=tree[k*2].sum+tree[k*2+1].sum;
    		tree[k].len=tree[k*2].len+tree[k*2+1].len;
    	}
    	
    	inline void build(ll k,ll l,ll r)
    	{
    		if(l==r)
    		{
    			tree[k].len=1;
    			tree[k].sum=a[l];
    			return;
    		}
    		ll mid=l+r>>1;
    		build(k*2,l,mid);
    		build(k*2+1,mid+1,r);
    		pushup(k);
    	}
    	
    	inline void change(ll k,ll l,ll r,ll z,ll y,ll x)
    	{
    		if(l==z&&r==y)
    		{
    			A(k,x);
    			return;
    		}
    		if(tree[k].ad) pushdown(k);
    		ll mid=l+r>>1;
    		if(y<=mid) change(k*2,l,mid,z,y,x);
    		else if(z>=mid+1) change(k*2+1,mid+1,r,z,y,x);
    		else change(k*2,l,mid,z,mid,x),change(k*2+1,mid+1,r,mid+1,y,x);
    		pushup(k);
    	}
    	
    	inline ll ask_sum(ll k,ll l,ll r,ll z,ll y)
    	{
    		if(l==z&&r==y) return tree[k].sum;
    		if(tree[k].ad) pushdown(k);
    		ll mid=l+r>>1;
    		if(y<=mid) return ask_sum(k*2,l,mid,z,y);
    		else if(z>=mid+1) return ask_sum(k*2+1,mid+1,r,z,y);
    		else return ask_sum(k*2,l,mid,z,mid)+ask_sum(k*2+1,mid+1,r,mid+1,y);
    	}
    };
    
    Stree stree;
    
    int main()
    {
    	scanf("%lld%lld",&n,&m);
    	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    	stree.build(1,1,n);
    	
    	for(int i=1;i<=m;i++)
    	{
    		int op,l,r;
    		scanf("%d%d%d",&op,&l,&r);
    		if(op==1)
    		{
    			ll x;
    			scanf("%lld",&x);
    			stree.change(1,1,n,l,r,x);
    		}
    		else printf("%lld
    ",stree.ask_sum(1,1,n,l,r));
    	}
    }
    

    线段树2代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<sstream>
    #include<queue>
    #include<vector>
    #define N 1000010
    #define ll long long
    using namespace std;
    ll n,m,p;
    ll sum[N<<2],len[N<<2],a[N],ad_1[N<<2],ad_2[N<<2];
    
    void pushup(ll k)
    {
    	sum[k]=(sum[k*2]+sum[k*2+1])%p;
    	len[k]=len[k*2]+len[k*2+1];
    }
    
    void A(ll k,ll x)
    {
    	sum[k]=(x*sum[k])%p;
    	ad_1[k]=(ad_1[k]*x)%p;
    	ad_2[k]=(ad_2[k]*x)%p;
    	if(ad_1[k]==0) ad_1[k]=p;
    }
    
    void B(ll k,ll x)
    {
    	sum[k]=(sum[k]+(len[k]*x)%p)%p;
    	ad_2[k]=(ad_2[k]+x)%p;
    }
    
    void pushdown_2(ll k)
    {
    	B(k*2,ad_2[k]);
    	B(k*2+1,ad_2[k]);
    	ad_2[k]=0;
    }
    
    void pushdown_1(ll k)
    {
    	A(k*2,ad_1[k]);
    	A(k*2+1,ad_1[k]);
    	ad_1[k]=1;
    }
    
    void change_1(ll k,ll l,ll r,ll z,ll y,ll x)
    {
    	if(l==z&&r==y)
    	{
    		A(k,x);
    		return;
    	}
    	if(ad_1[k]>1) pushdown_1(k);
    	if(ad_2[k]) pushdown_2(k);
    	ll mid=l+r>>1;
    	if(y<=mid) change_1(k*2,l,mid,z,y,x);
    	else if(mid<z) change_1(k*2+1,mid+1,r,z,y,x);
    	else
    	{
    		change_1(k*2,l,mid,z,mid,x);
    		change_1(k*2+1,mid+1,r,mid+1,y,x);
    	}
    	pushup(k);
    }
    
    int ask_sum(ll k,ll l,ll r,ll z,ll y)
    {
    	if(l==z&&r==y)
    	{
    		return sum[k];
    	}
    	if(ad_1[k]>1) pushdown_1(k);
    	if(ad_2[k]) pushdown_2(k);
    	ll mid=l+r>>1;
    	if(y<=mid) return ask_sum(k*2,l,mid,z,y);
    	else if(mid<z) return ask_sum(k*2+1,mid+1,r,z,y);
    	else return (ask_sum(k*2,l,mid,z,mid)%p+ask_sum(k*2+1,mid+1,r,mid+1,y)%p)%p;
    }
    
    void change_2(ll k,ll l,ll r,ll z,ll y,ll x)
    {
    	if(l==z&&r==y)
    	{
    		B(k,x);
    		return;
    	}
    	if(ad_1[k]>1) pushdown_1(k);
    	if(ad_2[k]) pushdown_2(k);
    	ll mid=l+r>>1;
    	if(y<=mid) change_2(k*2,l,mid,z,y,x);
    	else if(mid<z) change_2(k*2+1,mid+1,r,z,y,x);
    	else
    	{
    		change_2(k*2,l,mid,z,mid,x);
    		change_2(k*2+1,mid+1,r,mid+1,y,x);
    	}
    	pushup(k);
    }
    
    void build(ll k,ll l,ll r)
    {
    	ad_1[k]=1;
    	if(l==r)
    	{
    		sum[k]=a[l];
    		len[k]=1;
    		return;
    	}
    	ll mid=l+r>>1;
    	build(k*2,l,mid);
    	build(k*2+1,mid+1,r);
    	pushup(k);
    }
    
    
    void print()
    {
    	cout<<endl<<endl;
    	int i=0;
    	do
    	{
    		cout<<sum[++i]<<" ";
    	}while(sum[i]);
    	cout<<endl<<endl;
    }
    
    
    int main()
    {
    //	freopen("he.in","r",stdin);
    //	freopen("he.out","w",stdout);
    	ios::sync_with_stdio(false);
    	cin>>n>>m>>p;
    	for(int i=1;i<=n;i++) cin>>a[i];
    	
    	build(1,1,n);
    	//print();
    	
    	while(m--)
    	{
    		int op,a,b,x;
    		cin>>op>>a>>b;
    		if(op==1)
    		{
    			cin>>x;
    			change_1(1,1,n,a,b,x);
    			//print();
    		}
    		else if(op==2)
    		{
    			cin>>x;
    			change_2(1,1,n,a,b,x);
    			//print();
    		}
    		else
    		{
    			int ans=ask_sum(1,1,n,a,b);
    			cout/*<<"ans="*/<<ans<<endl;
    			//print();
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    软件评测师笔记(十二)—— 口令攻击相关
    软件评测师笔记(十一)—— 可靠性测试相关
    软件评测师笔记(十)—— 安全测试相关
    软件评测师笔记(九)—— 性能测试相关
    【猫狗数据集】利用tensorboard可视化训练和测试过程
    【colab pytorch】使用tensorboardcolab可视化
    【colab pytorch】使用tensorboard可视化
    【colab pytorch】FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy
    【猫狗数据集】使用学习率衰减策略并边训练边测试
    【猫狗数据集】划分验证集并边训练边验证
  • 原文地址:https://www.cnblogs.com/TianMeng-hyl/p/12233023.html
Copyright © 2011-2022 走看看