[线段树&树状数组] P3374 【模板】树状数组 1题解

目录

目录

附录

题目

[题目描述]

[输出格式]

[输入输出样例]

[输入样例 #1]

[输出样例 #1]

[说明/提示]

[数据范围]

[样例说明]

解法

 1.树状数组

2.线段树

I.结构体

II.初始化线段树

III.单点修改

IV.回答

V.代码

总结

附录

线段树从入门到进阶第1题

题目弹射器:【模板】树状数组 1 - 洛谷

题目

先来看题:

[题目描述]

如题,已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 𝑥

  • 求出某区间每一个数的和

接下来 𝑚 行每行包含 3 个整数,表示一个操作,具体如下:

  • 1 x k 含义:将第 𝑥 个数加上 𝑘

  • 2 x y 含义:输出区间 [𝑥,𝑦] 内每个数的和

[输出格式]

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

[输入输出样例]

[输入样例 #1]

5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4

[输出样例 #1]

14
16

[说明/提示]

[数据范围]

对于 30% 的数据,1≤𝑛≤8,1≤𝑚≤10;
对于 70% 的数据,1≤𝑛,𝑚≤10⁴;
对于 100% 的数据,1≤𝑛,𝑚≤5×10⁵。

数据保证对于任意时刻,𝑎 的任意子区间(包括长度为 1 和 𝑛的子区间)和均在 [−231,231)范围内。

[样例说明]

故输出结果14、16

解法

 1.树状数组

这是一道树状数组的模板题,对于lowbit、modify和query函数这里将不再赘述,不会的同学请移步这边:树状数组概览-CSDN博客。如果你会线段数且不想学习树状数组,建议你直接跳到线段树解法食用。

学会了树状数组的三个重要函数,这道题目就可以直接做出来了,下面是代码:

// P3374 【模板】树状数组 1 
#include <bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
int n,m,num,ask,x,y,tree[maxn];
int lowbit(int id) {
	return id&(-id);
}
void modify(int id,int k) {
	while (id<=n) {
		tree[id]+=k;
		id+=lowbit(id);
	}
	return;
}
int query(int id) {
	int ans=0;
	while (id!=0) {
		ans+=tree[id];
		id-=lowbit(id);
	}
	return ans;
}
int main() {
	scanf("%d %d",&n,&m);
	for (int i=1;i<=n;i++) {
		scanf("%d",&num);
		modify(i,num);
	}
	for (int i=1;i<=m;i++) {
		scanf("%d %d %d",&ask,&x,&y);
		if (ask==1)
			modify(x,y);
		else
			cout<<query(y)-query(x-1)<<"\n";
	}
	return 0;
}

2.线段树

(线段树这里的函数名可能不太规范,大家按自己喜好来就可以)

同样,如果你不会线段树,请移步这边:线段树概览-CSDN博客

如果你已经阅读上面博客,了解线段树大致思路。接下来所要做的就是就是将代码里用...省略的部分补充完整。

I.结构体

很明显,结构体只需要添加一个记录区间和的sum变量即可:

struct Tree {
	int l,r,sum;
}tree[maxm];

II.初始化线段树

当这一个节点的区间的l==r时,我们要将tree[id].sum设为当前区间所代表的数,利用num[]数组存储,则有tree[id].sum=num[tree[id].l]。如果不是,往下遍历后将当前区间和设为两个孩子节点区间和的和,即tree[id].sum=tree[id*2].sum+tree[id*2+1].sum。以下是代码:

void build(int l,int r,int id) { // 构建线段树 
	tree[id].l=l;
	tree[id].r=r;
	if (l==r) {
        tree[id].sum=num[tree[id].l];
		return;
    }
	int mid=(l+r)/2;
	build(l,mid,id*2);
	build(mid+1,r,id*2+1);
    tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
	return;
}

III.单点修改

很明显,每次查询只需给当前和加上需要加的数即可。以下是代码:

void push_down(int id,int k,int dis) { // 单点修改  
	tree[id].sum+=k;
	if (tree[id].l==tree[id].r)
		return;
	int mid=(tree[id].l+tree[id].r)/2;
	if (dis<=mid)
		push_down(id*2,k,dis);
	else
		push_down(id*2+1,k,dis);
	return;
}

IV.回答

回答也是比较明显的,判断如果在直接将tree[id].sum加入ans中。否则,判断是否有重叠片段,继续搜索。下面是代码:

void push_up(int id,int l,int r) { // 查询答案  
	if (tree[id].l>=l&&tree[id].r<=r) {
		ans+=tree[id].sum;
		return;
	}
	if (tree[id*2].r>=l)
		push_up(id*2,l,r);
	if (tree[id*2+1].l<=r)
		push_up(id*2+1,l,r);
	return;
}

V.代码

// 线段树  
#include <bits/stdc++.h>
using namespace std;
const int maxx=5e5+5;
const int maxn=5e5*4+5;
int n,m,ans,num[maxx];
struct Tree {
	int l,r,sum;
}tree[maxn];
void build(int l,int r,int id) { // 构建线段树 
	tree[id].l=l;
	tree[id].r=r;
	if (l==r) {
        tree[id].sum=num[tree[id].l];
		return;
    }
	int mid=(l+r)/2;
	build(l,mid,id*2);
	build(mid+1,r,id*2+1);
    tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
	return;
}
void push_down(int id,int k,int dis) { // 单点修改  
	tree[id].sum+=k;
	if (tree[id].l==tree[id].r)
		return;
	int mid=(tree[id].l+tree[id].r)/2;
	if (dis<=mid)
		push_down(id*2,k,dis);
	else
		push_down(id*2+1,k,dis);
	return;
}
void push_up(int id,int l,int r) { // 查询答案  
	if (tree[id].l>=l&&tree[id].r<=r) {
		ans+=tree[id].sum;
		return;
	}
	if (tree[id*2].r>=l)
		push_up(id*2,l,r);
	if (tree[id*2+1].l<=r)
		push_up(id*2+1,l,r);
	return;
}
int main() {
	scanf("%d %d",&n,&m);
	for (int i=1;i<=n;i++)
		scanf("%d",&num[i]);
	build(1,n,1);
	for (int i=1;i<=m;i++) {
		int ask,x,y;
		scanf("%d %d %d",&ask,&x,&y);
		if (ask==1)
			push_down(1,y,x);
		else {
			ans=0;
			push_up(1,x,y);
			cout<<ans<<"\n";
		}
	}
	return 0;
} 

总结

纯模板题,第一次写可能会漏掉一些细节,学会模板就大概率可以轻松写出来了。

喜欢就点个赞吧!

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值