树状数组(区间修改,区间查询)


树状数组(区间修改,区间查询)

原创 已于 2023-12-04 19:11:20 修改 · 粉丝可见 · 1.2k 阅读 · 10 · 15 · 本内容遵循CC 4.0 BY-SA版权协议 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 GEO检测 · 编辑
文章链接:https://blog.csdn.net/hacker_51/article/details/134790835

本题链接: 用户登录

题目:

样例:

cobol<br/>5 5<br/>1 2 3 4 5<br/>2 1 2<br/>1 2 3 1<br/>2 1 3<br/>1 1 5 1<br/>2 1 5<br/>
cobol<br/>3<br/>8<br/>22<br/>

思路:

树状数组的区间修改,区间查询,操作起来有点繁琐了些,但还是可以理解的。

这里区间修改,我们还是需要需要通过差分来进行区间修改的。

不同的是,在于区间查询这一块。

我们先模拟一遍区间修改后,差分数组 dif,原数组 a

得:                差分数组的前缀和,就是获得当前原数组的数值

1
2
3
4
5
6
7
dif[1] = a[1]
dif[2] = a[2] - a[1]
dif[3] = a[3] - a[2]
dif[4] = a[4] - a[3]
...
...
dif[n] = a[n] - a[n-1]

而我们需要求的是区间和,所以当我们设前缀和数组为 sum可知

1
2
3
4
5
6
sum[1] = dif[1];
sum[2] = (dif[2] + dif[1]) + sum[1];
sum[3] = (dif[3] + dif[2] + dif[1]) + sum[2];
sum[4] = (dif[4] + dif[3] + dif[2] + dif[1]) + sum[3];
...
sum[n] = (dif[n] + dif[n-1] + ... + dif[1]) + sum[n-1];

所以,当我们区间查询区间求和的时候

可以得出,我们区间求和, 相应的差分求和的求和具有一定的规律性。

此时我们将其化为差分,可以得知,减去剩余的 dif[] 随即递增的累乘

1
2
3
4
5
6
7
8
9
10
difSum[1] = sum[1];
difSum[2] = sum[2] - sum[1];
difSum[3] = sum[3] - sum[2];
difSum[4] = sum[4] - sum[3];
...
difSum[n] = sum[n] - sum[n-1];


difSum[n] = (dif[n] + dif[n - 1] + ... + dif[1])*(n + 1) -
(dif[1] * 1 + dif[2] * 2 + ... + dif[n] * n);

所以我们需要 两个差分数组 来进行区间修改以及区间查询。

一个 dif 为 正常的 (dif[n] + dif[n - 1] + ... + dif[1]) 的前缀和差分数组;

一个 p_dif 为 (dif[1] * 1 + dif[2] * 2 + ... + dif[n] * n)的前缀和差分数组;

还有前缀和函数  sum

最后通过前缀和相减,就可以获得区间和了

操作函数如下:

cpp<br/>// 单点修改函数<br/>inline void Add_pos(int tr[],int pos,int x)<br/>{<br/> for(int i = pos;i <= n + 1;i+=lowbit(i)) tr[i] += x;<br/>}<br/>
cpp<br/>// 获取前缀和函数<br/>inline int getSum(int tr[],int pos)<br/>{<br/> int res = 0;<br/> for(int i = pos;i;i-=lowbit(i)) res += tr[i];<br/> return res;<br/>}<br/>
cpp<br/>// 区间修改加值<br/>inline void Add_section(int l,int r,int x)<br/>{<br/> // 差分加值 dif<br/> Add_pos(dif,l,x);<br/> Add_pos(dif,r+1,-x);<br/> <br/> // 差分加值 p_dif<br/> Add_pos(p_dif,l,l*x);<br/> Add_pos(p_dif,r+1,(r+1)*(-x));<br/>}<br/>
cpp<br/>// 获取 Sum 的前缀和<br/>inline int get_difSum(int pos)<br/>{<br/> // 求 sum 的前缀和函数<br/> return getSum(dif,pos)*(pos + 1) - getSum(p_dif,pos);<br/>}<br/>
cpp<br/>// 获取区间前缀和<br/>inline int Ask_section(int l,int r)<br/>{<br/> return get_difSum(r) - get_difSum(l-1);<br/>}<br/>

代码详解如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
#include <unordered_map>
#define endl '\n'
#define int long long
#define YES puts("YES")
#define NO puts("NO")
#define umap unordered_map
#define All(x) x.begin(),x.end()
#pragma GCC optimize(3,"Ofast","inline")
#define IOS std::ios::sync_with_stdio(false),cin.tie(0), cout.tie(0)
using namespace std;
inline int lowbit(int x){return (x&(-x));} // 定义 lowbit()
const int N = 2e6 + 10;
int n,m;
int dif[N],p_dif[N]; // 定义 dif 原差分数组,减去的剩余差分数组 p_dif

// 单点修改函数
inline void Add_pos(int tr[],int pos,int x)
{
for(int i = pos;i <= n + 1;i+=lowbit(i)) tr[i] += x;
}

// 获取前缀和函数
inline int getSum(int tr[],int pos)
{
int res = 0;
for(int i = pos;i;i-=lowbit(i)) res += tr[i];
return res;
}

// 区间修改加值
inline void Add_section(int l,int r,int x)
{
// 差分加值 dif
Add_pos(dif,l,x);
Add_pos(dif,r+1,-x);

// 差分加值 p_dif
Add_pos(p_dif,l,l*x);
Add_pos(p_dif,r+1,(r+1)*(-x));
}

// 获取 Sum 的前缀和
inline int get_difSum(int pos)
{
// 求 sum 的前缀和函数
return getSum(dif,pos)*(pos + 1) - getSum(p_dif,pos);
}

// 获取区间前缀和
inline int Ask_section(int l,int r)
{
return get_difSum(r) - get_difSum(l-1);
}

inline void solve()
{
cin >> n >> m;
for(int i = 1,r = 0,a,x;i <= n;++i)
{
cin >> a; // 输入原数组 初始值
x = a - r; // 获取差分值

Add_pos(dif,i,x); // 插入差分值

Add_pos(p_dif,i,i*x); // 插入需减掉的剩余差分值

r = a; // 记忆化上一个元素
}

while(m--)
{
int op;
cin >> op;
if(op == 1)
{
int l,r,x;
cin >> l >> r >> x;
Add_section(l,r,x); // 区间修改
}else
{
int l,r;
cin >> l >> r;
cout << Ask_section(l,r) << endl; // 区间查询
}
}
}
signed main()
{
// freopen("a.txt", "r", stdin);
IOS;
int _t = 1;
// cin >> _t;
while (_t--)
{
solve();
}

return 0;
}

最后提交:


觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭

微信二维码

wechat pay

支付宝二维码

ali pay

树状数组(区间修改,区间查询)
http://blog.angindem.cn/2023/12/04/Angindem-CSDN博客/112_112/
作者
Angindem
发布于
2023年12月4日
许可协议