线段树与树状数组

线段树是指一个用树形结构来维护数列的一个数据结构。
通常而言,我们将其分为log2n 层,然后每层就是将上一层的区间分成两个部分。则通常线段树空间开四倍,时间O(n log n),不难发现,它可以在一个平衡的时间空间复杂度下维护区间。缺点在于只能维护一个连续的区间。当然线段树少不了lazy,它作为一个区间操作后延的操作,注意要清空它,而且注意要下传。

以下是 :线段树2 的代码:

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,len=0,a[200001],mod;
struct TR 
{
	int l,r,lc,rc,sum,lazy1,lazy2;
};TR tr[400001];
int bt(int x,int y)
{
	int now=++len,l=x,r=y,lc=-1,rc=-1,sum=0;
	if(l==r) sum=a[x];
	else 
	{
		int mid=(l+r)>>1;
		lc=bt(l,mid);rc=bt(mid+1,r);sum=tr[lc].sum+tr[rc].sum;
	}sum%=mod;
	tr[now]={l,r,lc,rc,sum,1,0};
	return now;
}
//先做乘法,然后前面加的要乘上这个就行了。 
void pushdown(int now)
{
	int lc=tr[now].lc,rc=tr[now].rc;
	tr[lc].sum=(tr[lc].sum*tr[now].lazy1+(tr[lc].r-tr[lc].l+1)*tr[now].lazy2)%mod;
	tr[rc].sum=(tr[rc].sum*tr[now].lazy1+(tr[rc].r-tr[rc].l+1)*tr[now].lazy2)%mod;
	tr[lc].lazy1=(tr[lc].lazy1*tr[now].lazy1)%mod;tr[lc].lazy2=(tr[lc].lazy2*tr[now].lazy1+tr[now].lazy2)%mod;
	tr[rc].lazy1=(tr[rc].lazy1*tr[now].lazy1)%mod;tr[rc].lazy2=(tr[rc].lazy2*tr[now].lazy1+tr[now].lazy2)%mod;
	tr[now].lazy1=1;tr[now].lazy2=0;
	return ;
}
void mul(int now,int x,int y,int k)
{
	int l=tr[now].l,r=tr[now].r;
	if(l==x&&y==r) tr[now].sum=(tr[now].sum*k)%mod,tr[now].lazy1*=k,tr[now].lazy2=(tr[now].lazy2*k)%mod;
	else 
	{
		int lc=tr[now].lc,rc=tr[now].rc,mid=(l+r)>>1;pushdown(now);
		if(y<=mid) mul(lc,x,y,k);
		else if(x>=mid+1) mul(rc,x,y,k);
		else mul(lc,x,mid,k),mul(rc,mid+1,y,k);
		tr[now].sum=(tr[lc].sum+tr[rc].sum)%mod;
	}
	return ;
}
void add(int now,int x,int y,int k)
{
	int l=tr[now].l,r=tr[now].r;
	if(l==x&&y==r) tr[now].sum=(tr[now].sum+k*(r-l+1))%mod,tr[now].lazy2=(tr[now].lazy2+k)%mod;
	else 
	{
		int lc=tr[now].lc,rc=tr[now].rc,mid=(l+r)>>1;pushdown(now);
		if(y<=mid) add(lc,x,y,k);
		else if(x>=mid+1) add(rc,x,y,k);
		else add(lc,x,mid,k),add(rc,mid+1,y,k);
		tr[now].sum=(tr[lc].sum+tr[rc].sum)%mod;
	}
	return ;
}
int findans(int now,int x,int y)
{
	int l=tr[now].l,r=tr[now].r;//printf("*");
	if(l==x&&y==r) return tr[now].sum;
	else
	{
		int lc=tr[now].lc,rc=tr[now].rc,mid=(l+r)>>1;pushdown(now);
		if(y<=mid) return findans(lc,x,y);
		else if(x>=mid+1) return findans(rc,x,y);
		else return (findans(lc,x,mid)+findans(rc,mid+1,y))%mod;
	}	
}
signed main()
{
	int q;scanf("%lld%lld%lld",&n,&q,&mod);for(int i=1;i<=n;i++) scanf("%lld",&a[i]);int root=bt(1,n);
	while(q--)
	{
		int op;scanf("%lld",&op);
		if(op==1) 
		{
			int x,y,k;scanf("%lld%lld%lld",&x,&y,&k);mul(root,x,y,k);
		}
		else if(op==2) 
		{
			int x,y,k;scanf("%lld%lld%lld",&x,&y,&k);add(root,x,y,k);
		}
		else 
		{
			int x,y;scanf("%lld%lld",&x,&y);printf("%lld\n",findans(root,x,y)%mod);
		}
//		for(int i=1;i<=n;i++) printf("%d ",findans(root,i,i)%mod);printf("\n");
	}
	return 0;
} 

树状数组的时间与复杂度均会优秀一点,但是通常而言,区间修改与区间查询二者只能取其一(实际上有很牛的做法但我不会)。lowbit指的是一个二进制数的最低位1的位置。体现的是树状数组用二进制位运算保存区间值的思想。如何做到单点修改区间查询?在对应的二进制位以及包含它的位置上加,然后直接统计即可。单点查询区间修改?则转化为差分就好了。
对于lowbit的理解:https://www.cnblogs.com/fusiwei/p/11752540.html

树状数组1

//区间管辖嘛 
//lowbit实际上取的是最左边的1的位置代表的数; 
#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
using namespace std;
int n,m,f[4000001];
void add(int x,int k)
{
	for(x;x<=n+1;x+=lowbit(x)) f[x]+=k;
	return ;
}
int findans(int x)
{
	int rt=0;for(x;x;x-=lowbit(x)) rt+=f[x];
	return rt;
}
int main()
{
	memset(f,0,sizeof(f));
	int q;scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)
	{
		int k;scanf("%d",&k);add(i,k);
	}
	while(q--)
	{
		int op;scanf("%d",&op);
		if(op==1)
		{
			int x,k;scanf("%d%d",&x,&k);add(x,k);	
		} 
		else 
		{
			int x,y;scanf("%d%d",&x,&y);printf("%d\n",findans(y)-findans(x-1));
		}
	}
	return 0;	
}

P3368 【模板】树状数组 2

//改成差分,保存第i位置对于前面加多少减多少。
//则如果要求区间和的话要做两次的前缀和 
#include<bits/stdc++.h>
#define lowbit(x) x&(-x) 
using namespace std;
int n,m,f[2000001];
void add(int x,int k)
{
	for(x;x<=n+1;x+=lowbit(x)) f[x]+=k;
	return ;
}
void addrange(int x,int y,int k)
{
	add(y+1,-k);add(x,k);
	return ;
}
int findans(int x)
{
	int rt=0;for(x;x;x-=lowbit(x)) rt+=f[x];
	return rt;
}
int main() 
{
	memset(f,0,sizeof(f));
	int q;scanf("%d%d",&n,&q);
	int lastone=0;for(int i=1;i<=n;i++)
	{
		int k;scanf("%d",&k);add(i,k-lastone);lastone=k;
	}
	while(q--)
	{
		int op;scanf("%d",&op);
		if(op==1) 
		{
			int x,y,k;scanf("%d%d%d",&x,&y,&k);addrange(x,y,k);
		}
		else 
		{
			int x;scanf("%d",&x);printf("%d\n",findans(x));
		}
	}
	return 0;
}

切一会水题:P5142 区间方差

如果有时间就写,思路大概是维护区间平方和以及区间和,然后乱搞。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值