前缀和、差分、树状数组、线段树

前缀和

应用:区间查询,不涉及数的变化。求区间[l,r]d的和

一维前缀和:
s[i] = a[1] + a[2] + … + a[i];
s[i] = s[i-1] + a[i];
二维前缀和:
s[i][j] = 第 i 行第 j 列格子左上部分所有元素的和
以(x1,y1)为左上角,(x2,y2)为右下角
s[x2,y2] - s[x2, y1-1] - s[x1-1, y2] + s[x1-1,y1-1]

差分

应用:前缀和的逆运算,给区间[l,r]中每个数加上c

一维差分:
B[l] += c, B[r+1] -= c
二维差分:
以(x1,y1)为左上角,(x2,y2)为右下角的子矩阵中所有元素加上c
s[x1,y1] += c, s[x1, y2+1] -= c, s[x2+1,y1] -=c, s[x2+1,y2+1] += c

树状数组:

应用:树状数组作为线段树中的一部分,可以进行单点修改和区间查询。时间复杂度为O(logn)
树状数组结构图在这里插入图片描述
树状数组、线段树裸题

给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b] 的连续和。

输入格式 第一行包含两个整数 n 和 m,分别表示数的个数和操作次数。

第二行包含 n 个整数,表示完整数列。

接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和;k=1,表示第 a 个数加 b)。

数列从 1 开始计数。

输出格式 输出若干行数字,表示 k=0 时,对应的子数列 [a,b] 的连续和。

在这里插入图片描述

注意点:
1.有三个核心函数。lowbit(int n) 返回数字n的最后一位1
2. query(int x) tr[x]表示的是(x-2^k, x]的和

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100010;
 
int n, m;
int a[N];
int tr[N];

int lowbit(int x)
{
	return x & -x;
}

int query(int x)
{
	int res = 0;
	for(int i=x; i; i-=lowbit(i)) res += tr[i]; 
	return res;
}

void modify(int a, int b)
{
	for(int i=a; i<=n; i+=lowbit(i)) tr[i] += b;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++) scanf("%d",&a[i]);
	for(int i=1; i<=n; i++) modify(i,a[i]);
	int k, a, b;
	while(m--)
	{
		scanf("%d%d%d",&k,&a,&b);
		if(k == 0) printf("%d\n",query(b)-query(a-1));
		else modify(a,b); 
	}
	return 0;
}

线段树

应用:应用范围广。染色、面积等等(还没有学习到)。懒标记(只知道这个名词)

注意点:
1.数据范围4*N
2.很容易出错
线段树结构图在这里入图片描述

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100010;
int n, m;
int w[N];

struct Node{
	int l, r;
	int sum;
}tr[4*N];

void pushup(int u)
{
	tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;	
} 

void build(int u, int l, int r)
{
	if(l == r) tr[u] = {l, r, w[r]};
	else
	{
		tr[u] = {l,r};
		int mid = l + r >> 1;
		build(u << 1, l, mid), build(u << 1 | 1, mid+1, r);
		pushup(u);
	}
}

int query(int u, int l, int r)
{
	if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
	
	int sum = 0;
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid)  sum += query(u << 1, l, r);
	if(r > mid) sum += query(u << 1 | 1, l, r);
	return sum;
}

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 >> 1;
		if(x <= mid) modify(u << 1, x, v);
		else modify(u << 1 | 1, x, v);
		pushup(u);
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++) scanf("%d",&w[i]);
	build(1,1,n);
	int k, a, b;
	while(m--)
	{
		scanf("%d%d%d",&k,&a,&b);
		if(k == 0) printf("%d\n",query(1,a,b));
		else modify(1, a, b);
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值