小结线段树

例题——洛谷P3374

题目描述

如题,已知一个数列,你需要进行下面两种操作:
将某一个数加上 x
求出某区间每一个数的和

输入格式

第一行包含两个正整数 n,m分别表示该数列数字的个数和操作的总个数。
第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:
1 x k 含义:将第 x 个数加上 k
2 x y 含义:输出区间 [x,y]内每个数的和

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

输入输出样例

输入
5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
输出
14
16
【数据范围】
对于100% 的数据,1≤n,m≤5×10^5

思路与分析

很明显我们要对一段给出的区间进行操作,对区间求和,然后单点修改, 如果没有单点修改,我们用前缀和就能实现O(n)的时间复杂度,但是涉及到了修改,前缀和就显得无能为力,就需要O(n^2)的时间复杂度,所以我们引入树形结构来进行这个维护和查询工作,单次查询O(logn).

线段树

基础线段树模板一共有三种操作,建树,修改和查询。除了建树是O(n)外,其他都为O(logn),虽然修改操作从O(1)变到了二分查找,但是因为特殊的树形结构使得查询也是O(logn)。

树形结构的定义

typedef long long ll;
struct node
{
	int l,r;
	ll sum;
};
ll arr[500100];
node tree[2000020];

建树

选择递归来建树

void build(int p,int l,int r)//p是树形数组里的下表
{
	tree[p].l=l;
	tree[p].r=r;
	if(l==r)
	{
		tree[p].sum=arr[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build((p<<1)|1,mid+1,r);
	tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;//维护和
}

查询

ll _find(int p,int l,int r)
{
	ll sum=0;
	if(l<=tree[p].l&&tree[p].r<=r)
		return tree[p].sum;
	if(tree[p].r<l||tree[p].l>r)  
		return 0;
	if(tree[(p<<1)|1].l<=r)
		sum+= _find((p<<1)|1,l,r);
	if(tree[p<<1].r>=l)
		sum+= _find(p<<1,l,r);
	return sum;
}

单点修改

void add(int x,int p,ll k)
{
	if(tree[p].l==tree[p].r)
	{
		tree[p].sum+=k;
		return ;
	}
	if(x<=tree[p<<1].r)
		add(x,p<<1,k);
	else
		add(x,(p<<1)|1,k);
	tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
	return ;
}

例题完整代码

#include<iostream>
using namespace std;
typedef long long ll;
struct node
{
	int l,r;
	ll sum;
};
ll arr[500100];
node tree[2000020];
void build(int p,int l,int r)
{
	tree[p].l=l;
	tree[p].r=r;
	if(l==r)
	{
		tree[p].sum=arr[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build((p<<1)|1,mid+1,r);
	tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
}
ll _find(int p,int l,int r)
{
	ll sum=0;
	if(l<=tree[p].l&&tree[p].r<=r)
		return tree[p].sum;
	if(tree[p].r<l||tree[p].l>r)  
		return 0;
	if(tree[(p<<1)|1].l<=r)
		sum+= _find((p<<1)|1,l,r);
	if(tree[p<<1].r>=l)
		sum+= _find(p<<1,l,r);
	return sum;
}
void add(int x,int p,ll k)
{
	if(tree[p].l==tree[p].r)
	{
		tree[p].sum+=k;
		return ;
	}
	if(x<=tree[p<<1].r)
		add(x,p<<1,k);
	else
		add(x,(p<<1)|1,k);
	tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
	return ;
}
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>arr[i];
	build(1,1,n);
	for(int i=0;i<m;i++)
	{
		ll a,b,c;
		cin>>a>>b>>c;
		if(a==1)
			add(b,1,c);
		else
			cout<<_find(1,b,c)<<endl;
	}
}

Lazy懒标记 洛谷P3368

题目描述

如题,已知一个数列,你需要进行下面两种操作:
将某区间每一个数数加上 x;求出某一个数的值。

输入格式

第一行包含两个整数 N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含 N 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 M 行每行包含 2 或 4个整数,表示一个操作,具体如下:
操作 1: 格式:1 x y k 含义:将区间 [x,y] 内每个数加上 kk;
操作 2: 格式:2 x 含义:输出第 x 个数的值。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

输入输出样例

输入
5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4
输出
6
10

定义

typedef long long ll;
struct node
{
	int l,r;
	ll sum;
	int lazy;
};
int arr[500010];
node tree[2000000];

建树

void build(int p,int l,int r)
{
	tree[p].l=l;
	tree[p].r=r;
	if(l==r)
	{
		tree[p].sum=arr[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build((p<<1)|1,mid+1,r);
	tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
}

pushdown函数

void pushdown(int p)
{
	int k=tree[p].lazy;
	if(k!=0)
	{
		tree[p<<1].lazy+=k;
		tree[(p<<1)|1].lazy+=k;
		tree[p<<1].sum+=k*(tree[p<<1].r-tree[p<<1].l+1);
		tree[(p<<1)|1].sum+=k*(tree[(p<<1)|1].r-tree[(p<<1)|1].l+1);
		tree[p].lazy=0;
	}
	return ;
}

区间修改

void add(int p,int l,int r,int k)//递归到p,给l->r区间内统一加上k 
{
	if(l<=tree[p].l&&tree[p].r<=r)
	{
		tree[p].lazy+=k;
		tree[p].sum+=k*(tree[p].r-tree[p].l+1);
		return ;
	}
	pushdown(p);
	if(tree[p<<1].r>=l)
		add(p<<1,l,r,k);
	if(tree[(p<<1)|1].l<=r)
		add((p<<1)|1,l,r,k);
	tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
	return ;
}

单点/区间查找

ll _find(int p,int l,int r)
{
    if(tree[p].l>=l&&tree[p].r<=r)
        return tree[p].sum;
    if(tree[p].r<l|| tree[p].l>r)
		return 0;
    pushdown(p);
    ll s=0;
    if(tree[p<<1].r>=l)  
		s+=_find(p<<1,l,r);
    if(tree[(p<<1)|1].l<=r)  
		s+=_find((p<<1)|1,l,r);
    return s;
}

最终函数

#include<iostream>
using namespace std;
typedef long long ll;
struct node
{
	int l,r;
	ll sum;
	int lazy;
};
int arr[500010];
node tree[2000000];
void build(int p,int l,int r)
{
	tree[p].l=l;
	tree[p].r=r;
	if(l==r)
	{
		tree[p].sum=arr[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build((p<<1)|1,mid+1,r);
	tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
}
void pushdown(int p)
{
	int k=tree[p].lazy;
	if(k!=0)
	{
		tree[p<<1].lazy+=k;
		tree[(p<<1)|1].lazy+=k;
		tree[p<<1].sum+=k*(tree[p<<1].r-tree[p<<1].l+1);
		tree[(p<<1)|1].sum+=k*(tree[(p<<1)|1].r-tree[(p<<1)|1].l+1);
		tree[p].lazy=0;
	}
	return ;
}
void add(int p,int l,int r,int k)//递归到p,给l->r区间内统一加上k 
{
	if(l<=tree[p].l&&tree[p].r<=r)
	{
		tree[p].lazy+=k;
		tree[p].sum+=k*(tree[p].r-tree[p].l+1);
		return ;
	}
	pushdown(p);
	if(tree[p<<1].r>=l)
		add(p<<1,l,r,k);
	if(tree[(p<<1)|1].l<=r)
		add((p<<1)|1,l,r,k);
	tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
	return ;
}
ll _find(int p,int l,int r)
{
    if(tree[p].l>=l&&tree[p].r<=r)
        return tree[p].sum;
    if(tree[p].r<l|| tree[p].l>r)
		return 0;
    pushdown(p);
    ll s=0;
    if(tree[p<<1].r>=l)  
		s+=_find(p<<1,l,r);
    if(tree[(p<<1)|1].l<=r)  
		s+=_find((p<<1)|1,l,r);
    return s;
}
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>arr[i];
	build(1,1,n);
	for(int i=0;i<m;i++)
	{
		int x;
		cin>>x;
		if(x==1)
		{
			int a,b,c;
			cin>>a>>b>>c;
			add(1,a,b,c);
		}
		else
		{
			int a,b;
			cin>>a;
			cout<<_find(1,a,a)<<endl;
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值