AcWing 1264. 动态求连续区间和
给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b] 的连续和。
输入格式
第一行包含两个整数 n 和 m,分别表示数的个数和操作次数。
第二行包含 n 个整数,表示完整数列。
接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和;k=1,表示第 a 个数加 b)。
数列从 1 开始计数。
输出格式
输出若干行数字,表示 k=0 时,对应的子数列 [a,b] 的连续和。
数据范围
1≤n≤100000,
1≤m≤100000,
1≤a≤b≤n
输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
1 1 5
0 1 3
0 4 8
1 7 5
0 4 8
输出样例:
11
30
35
相同题目的第二种做法,记录一下线段树的模板
定义结点类node,需要有左右端点l、r,及区域和值sum三个属性
建立线段树数组node tr[]=new node [N*4],各点权值暂存在int w[ ]中
首先编写更新结点函数pushup,由于线段树从上至下建立,每当插入一个新结点后其父节点及以上的权值sum都会发生改变,因此需要更新sum值,重新计算
pushup传参只需待更新结点坐标u
public static void pushup(int u) {
tr[u].sum=tr[u*2].sum+tr[u*2+1].sum;
}
接下来是建立线段树函数build,建立递归实现,首先新建结点,将其sum值定义为0,接下来递归建立左右子节点,最后通过pushup获得sum值,递归至结点的l=r时结束
build函数传参待建立结点坐标u,左端点l,右端点r
public static void build(int u,int l,int r) {
if(l==r)
tr[u]=new node(l, r, w[l]);
else {
tr[u]=new node(l, r, 0);
int mid=(l+r)/2;
build(u*2,l,mid);
build(u*2+1,mid+1,r);
pushup(u);
}
}
求和函数query,计算l到r的值时首先从根节点开始,先找出根节点的中心mid,其左子节点的范围是l~mid ,右子节点的范围是mid+1~r,如果左子结点的范围包含部分目标值,即mid>=l,则需要递归找到其包含部分的值累加,同理如果右子节点包含部分值,即mid+1<=r,同样需要累加这部分的值,递归至某结点完全被目标范围覆盖,递归结束
query传参为当前结点坐标u,目标范围l,r
public static int query(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r)
return tr[u].sum;
int mid=(tr[u].l+tr[u].r)/2;
int sum=0;
if(l<=mid)
sum=query(u*2, l, r);
if(r>=mid+1)//if(r>mid)
sum+=query(u*2+1, l, r);
return sum;
}
修改函数modify,在树中查询到坐标为x的结点,为其权值加v。从根节点开始查找,找到当前结点覆盖范围中点mid,如果x<=mid,则说明目标结点在右子节点中,递归至右子节点进行修改,反之则在左子结点。注意的是当递归结束后某个子节点的权值sum将会改变,因此需要进行pushup来更新父节点权值。递归在结点的左右范围相同,即某节点中只有一个元素时进行修改,递归结束
modify传参为当前结点坐标u,修改目标x,修改增量v
public static void modify(int u,int x,int v) {
if(tr[u].l==tr[u].r)
tr[u].sum+=v;
else {
int mid = (tr[u].l + tr[u].r )/ 2;
if(x<=mid)
modify(u*2,x,v);
else
modify(u*2+1,x,v);
pushup(u);
}
}
完整代码
import java.io.*;
import java.util.*;
public class Main {
static Scanner tab = new Scanner(System.in);
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
static int N = 100010;
static class node{
public int l;
public int r;
public int sum;
public node(int l, int r, int sum) {
super();
this.l = l;
this.r = r;
this.sum = sum;
}
}
static int n,m;
static node tr[]=new node [N*4];
static int w[]=new int [N];
public static void pushup(int u) {
tr[u].sum=tr[u*2].sum+tr[u*2+1].sum;
}
public static void build(int u,int l,int r) {
if(l==r)
tr[u]=new node(l, r, w[l]);
else {
tr[u]=new node(l, r, 0);
int mid=(l+r)/2;
build(u*2,l,mid);
build(u*2+1,mid+1,r);
pushup(u);
}
}
public static int query(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r)
return tr[u].sum;
int mid=(tr[u].l+tr[u].r)/2;
int sum=0;
if(l<=mid)
sum=query(u*2, l, r);
if(r>=mid+1)
sum+=query(u*2+1, l, r);
return sum;
}
public static void modify(int u,int x,int v) {
if(tr[u].l==tr[u].r)
tr[u].sum+=v;
else {
int mid = (tr[u].l + tr[u].r )/ 2;
if(x<=mid)
modify(u*2,x,v);
else
modify(u*2+1,x,v);
pushup(u);
}
}
public static void main(String[] args) throws IOException {
n=tab.nextInt();
m=tab.nextInt();
for(int i=1;i<=n;i++) {
w[i]=tab.nextInt();
}
build(1,1,n);
while(m-->0) {
int k=tab.nextInt();
int a=tab.nextInt();
int b=tab.nextInt();
if(k==0) {
System.out.println(query(1, a, b));
}
else {
modify(1, a, b);
}
}
}
}
这里如果追求极致的效率可以将乘除2求左右子节点坐标的操作换成 >>1或 <<1的位运算操作,不过在此题中影响不大,便于理解还是用的乘除操作