P3374 【模板】树状数组 1
【模板】树状数组 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
将某一个数加上 x x x
求出某区间每一个数的和
输入格式
第一行包含两个正整数 n , m n,m n,m,分别表示该数列数字的个数和操作的总个数。 第二行包含 n n n 个用空格分隔的整数,其中第 i i i
个数字表示数列第 i i i 项的初始值。接下来 m m m 行每行包含 3 3 3 个整数,表示一个操作,具体如下:
1 x k
含义:将第 x x x 个数加上 k k k
2 x y
含义:输出区间 [ x , y ] [x,y] [x,y] 内每个数的和输出格式
输出包含若干行整数,即为所有操作 2 2 2 的结果。
样例 #1
样例输入 #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 % 30\% 30% 的数据, 1 ≤ n ≤ 8 1 \le n \le 8 1≤n≤8, 1 ≤ m ≤ 10 1\le m \le 10 1≤m≤10; 对于 70 % 70\% 70% 的数据, 1 ≤ n , m ≤ 1 0 4 1\le n,m \le 10^4 1≤n,m≤104; 对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 5 × 1 0 5 1\le n,m \le 5\times 10^5 1≤n,m≤5×105。
数据保证对于任意时刻, a a a 的任意子区间(包括长度为 1 1 1 和 n n n 的子区间)和均在 [ − 2 31 , 2 31 ) [-2^{31}, 2^{31}) [−231,231)
范围内。样例说明:
故输出结果14、16
lowbit函数得到一个数仅保留最低位的1的值.
例如一个数i的二进制是 000…00110011100
那么lowbit(i)得到的数的二进制是000…00000000100
只保留最低为的1,其他的1全部变成0,这样的二进制所组成的数值就是lowbit(i)
lowbit(i)函数实际上只有一行,lowbit(i)=i&(-i).
树状数组解决的问题是,单点更新和范围查询.
原数组arr对应一个tree数组,树状数组.
tree数组数组的下标一定是从1开始的.
树状数组单点更新函数add(i,k)表示i位置加上k.
维护从i下标开始往后的部分tree值,全部加上k.
需要维护的下标分别是i+lowbit(i),i+2*lowbit(i)…直到越界.
树状数组范围查询函数range复用sum求前缀和操作.
sum(i)函数表示累加arr数组1~i前缀和值.arr数组下标也是从1开始表示,为了统一.
需要累加的tree下标从i下标开始往前的部分tree值.
需要用到的下标分别是i-lowbit(i),i-2*lowbit(i)…直到0.
range(l,r)函数等价于sum®-sum(l-1).
(1r)-(1l-1)等价于(l~r).
树状数组初始化是为了得到arr对应的tree值.
对于每一个位置调用add操作.
一定是从1开始.
#include<bits/stdc++.h>
using namespace std;
#define int long long // 使用long long类型来定义int
#define endl '\n' // 定义换行符为'\n'
int n, m; // n表示数列长度,m表示操作的总个数
vector<int> arr; // 存储数列的数组
struct node {
int op, x, y, k; // op表示操作类型,x和y表示操作的参数,k表示增加的值
};
vector<node> readd; // 存储所有操作的数组
class Tree {
public:
vector<int> tree; // 树状数组
// 计算lowbit
int lowbit(int i) {
return i & -i;
}
// 在树状数组中增加值
void add(int i, int k) {
while (i <= n) {
tree[i] += k;
i += lowbit(i);
}
}
// 计算前缀和
int sum(int i) {
int ret = 0;
while (i > 0) {
ret += tree[i];
i -= lowbit(i);
}
return ret;
}
// 计算区间和
int range(int l, int r) {
return sum(r) - sum(l - 1);
}
// 构造函数,用于初始化树状数组
Tree(vector<int> arr1) {
tree.assign(arr1.size(), 0); // 初始化树状数组
for (int i = 1; i < arr1.size(); i++) {
add(i, arr1[i]); // 初始化时将数组中的值加入树状数组
}
}
Tree() {} // 默认构造函数
};
Tree t1; // 定义树状数组对象
void solve() {
t1 = Tree(arr); // 初始化树状数组
for (auto& xx : readd) {
int op = xx.op, x = xx.x, y = xx.y, k = xx.k;
if (op == 1) {
t1.add(x, k); // 如果是增加操作,更新树状数组
} else {
cout << t1.range(x, y) << endl; // 如果是查询操作,输出区间和
}
}
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 提高输入输出效率
cin >> n >> m; // 读取数列长度和操作个数
arr.assign(n + 5, 0); // 初始化数组,大小为n+5
readd.clear(); // 清空操作数组
for (int i = 1; i <= n; i++) {
cin >> arr[i]; // 读取数列初始值
}
for (int i = 1; i <= m; i++) {
int op = 0, x = 0, y = 0, k = 0;
cin >> op; // 读取操作类型
if (op == 1) {
cin >> x >> k; // 读取增加操作的参数
} else {
cin >> x >> y; // 读取查询操作的参数
}
readd.push_back({ op, x, y, k }); // 将操作存储到操作数组中
}
solve(); // 处理所有操作
}
P3368 【模板】树状数组 2
【模板】树状数组 2
题目描述
如题,已知一个数列,你需要进行下面两种操作:
将某区间每一个数加上 x x x;
求出某一个数的值。
输入格式
第一行包含两个整数 N N N、 M M M,分别表示该数列数字的个数和操作的总个数。
第二行包含 N N N 个用空格分隔的整数,其中第 i i i 个数字表示数列第 $i $ 项的初始值。
接下来 M M M 行每行包含 2 2 2 或 4 4 4个整数,表示一个操作,具体如下:
操作 1 1 1: 格式:
1 x y k
含义:将区间 [ x , y ] [x,y] [x,y] 内每个数加上 k k k;操作 2 2 2: 格式:
2 x
含义:输出第 x x x 个数的值。输出格式
输出包含若干行整数,即为所有操作 2 2 2 的结果。
样例 #1
样例输入 #1
5 5 1 5 4 2 3 1 2 4 2 2 3 1 1 5 -1 1 3 5 7 2 4
样例输出 #1
6 10
提示
样例 1 解释:
故输出结果为 6、10。
数据规模与约定
对于 30 % 30\% 30% 的数据: N ≤ 8 N\le8 N≤8, M ≤ 10 M\le10 M≤10;
对于 70 % 70\% 70% 的数据: N ≤ 10000 N\le 10000 N≤10000, M ≤ 10000 M\le10000 M≤10000;
对于 100 % 100\% 100% 的数据: 1 ≤ N , M ≤ 500000 1 \leq N, M\le 500000 1≤N,M≤500000, 1 ≤ x , y ≤ n 1 \leq x, y \leq n 1≤x,y≤n,保证任意时刻序列中任意元素的绝对值都不大于 2 30 2^{30} 230。
数组数组一直都是单点更新和范围查询操作.
但是如果要进行范围更新和单点查询操作,可以进行一些操作得到.
例如我们arr数组希望对其进行范围更新和单点查询.
我们只需要对arr对应的差分数组进行单点更新和范围查询即可.
arr的差分数组
差分数组diff[i]=arr[i]-arr[i-1].
对于arr[i]可以理解从i位置开始累加arr[i],但是到i+1位置就停止,所以从i位置开始对后面的值的影响是arr[i],但是i+1位置开始就停止影响了,所以从i+1位置对后面的影响是-arr[i].
对arr数组范围(l~r)累加k.只需要维护diff[l]+=k,diff[r+1]-=k.
从l位置开始影响,影响值是k,到r+1结束影响,所以从r+1开始影响,影响值是-k.
只需要维护两个位置的值,单点更新.
查询arr数组某一个位置i的值,只需要求diff前缀和1~i的值.范围查询.
所以对arr进行范围更新和单点查询可以通过构建arr对应的diff差分数组,对diff差分数组进行单点更新和范围查询操作即可.
#include<bits/stdc++.h> // 引入标准库
using namespace std;
#define int long long // 定义长整型为int
#define endl '\n' // 定义换行符
int n, m; // n表示数列长度,m表示操作的总个数
vector<int> arr; // 存储数列的数组
vector<int> diff; // 存储差分数组
struct node {
int op, x, y, k; // op表示操作类型,x和y表示操作的参数,k表示增加的值
};
vector<node> readd; // 存储所有操作的数组
class Tree { // 树状数组类
public:
vector<int> tree; // 树状数组
// 计算lowbit
int lowbit(int i) {
return i & -i;
}
// 在树状数组中增加值
void add(int i, int k) {
while (i <= n) {
tree[i] += k;
i += lowbit(i);
}
}
// 计算前缀和
int sum(int i) {
int ret = 0;
while (i > 0) {
ret += tree[i];
i -= lowbit(i);
}
return ret;
}
// 计算区间和
int range(int l, int r) {
return sum(r) - sum(l - 1);
}
// 构造函数,用于初始化树状数组
Tree(vector<int> arr) {
tree.assign(arr.size(), 0); // 初始化树状数组
for (int i = 1; i < arr.size(); i++) {
add(i, arr[i]); // 初始化时将数组中的值加入树状数组
}
}
Tree() {} // 默认构造函数
};
Tree t1; // 定义树状数组对象
void init() { // 初始化函数
cin >> n >> m; // 读取数列长度和操作个数
arr.assign(n + 5, 0); // 初始化数组,大小为n+5
readd.clear(); // 清空操作数组
for (int i = 1; i <= n; i++) {
cin >> arr[i]; // 读取数列初始值
}
for (int i = 1; i <= m; i++) {
int op = 0, x = 0, y = 0, k = 0;
cin >> op; // 读取操作类型
if (op == 1) {
cin >> x >> y >> k; // 读取区间增加操作的参数
} else {
cin >> x; // 读取查询操作的参数
}
readd.push_back({ op, x, y, k }); // 将操作存储到操作数组中
}
}
void solve() {
diff.assign(arr.size(), 0); // 初始化差分数组
for (int i = 1; i < arr.size(); i++) {
diff[i] = arr[i] - arr[i - 1]; // 计算差分数组
}
t1 = Tree(diff); // 初始化树状数组
for (auto& xx : readd) { // 遍历所有操作
int op = xx.op, x = xx.x, y = xx.y, k = xx.k;
if (op == 1) { // 如果是增加操作
t1.add(x, k); // 在x位置增加k
t1.add(y + 1, -k); // 在y+1位置减少k
} else { // 如果是查询操作
cout << t1.sum(x) << endl; // 输出第x个数的值
}
}
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 提高输入输出效率
init(); // 初始化
solve(); // 处理所有操作
}
P3372 【模板】线段树 1
【模板】线段树 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
- 将某区间每一个数加上 k k k。
- 求出某区间每一个数的和。
输入格式
第一行包含两个整数 n , m n, m n,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。
接下来 m m m 行每行包含 3 3 3 或 4 4 4 个整数,表示一个操作,具体如下:
1 x y k
:将区间 [ x , y ] [x, y] [x,y] 内每个数加上 k k k。2 x y
:输出区间 [ x , y ] [x, y] [x,y] 内每个数的和。输出格式
输出包含若干行整数,即为所有操作 2 的结果。
样例 #1
样例输入 #1
5 5 1 5 4 2 3 2 2 4 1 2 3 2 2 3 4 1 1 5 1 2 1 4
样例输出 #1
11 8 20
提示
对于 30 % 30\% 30% 的数据: n ≤ 8 n \le 8 n≤8, m ≤ 10 m \le 10 m≤10。 对于 70 % 70\% 70% 的数据: n ≤ 10 3 n \le {10}^3 n≤103, m ≤ 10 4 m \le {10}^4 m≤104。 对于 100 % 100\% 100% 的数据: 1 ≤ n , m ≤ 10 5 1 \le n, m \le {10}^5 1≤n,m≤105。
保证任意时刻数列中所有元素的绝对值之和 ≤ 10 18 \le {10}^{18} ≤1018。
【样例解释】
对arr进范围更新和范围查询.
构建arr数组对应的差分数组diff可以维护对arr的范围更新和单点查询.
但是我们需要的是范围查询.
例如查询arr数组1~k范围的累加和.
就需要对diff差分数组进行操作sum(1)+sum(2)+…+sum(k).
即diff[1]+(diff[1]+diff[2])+…+(diff(1)+diff(2)+…+diff(k)).
即k*D1+(k-1)*D2+…+(1)*Dk
即k*(D1+D2+…+Dk)-(0*D1+1*D2+2*D3+…+(k-1)*Dk)
D1+D1+…+DK我们可以得到答案.
范围更新即可.
0*D1+1*D2+2*D3+…+(k-1)*Dk看作是C1+C2+C3+C4+…+CK同样是范围查询,构造对应的树状数组同样可以解决.
所以我们需要构造两个数组数组,一个是arr数组对应的差分数组,一个是(i-1)*差分数组的值的数组.
每一次都需要维护好这两个数组.
add(l,r,k),对应diff1[l]+=k,diff1[r+1]-=k.
diff2[l]+=(l-1)*k,diff2[r+1]-=(r+1-1)*k.
#define debug // 定义debug宏
#ifdef debug
// 如果定义了debug宏,定义show宏用于输出数组的内容
#define show(arr) do{for(auto&x:arr){cout<<x<<" ";} cout<<endl;}while(0)
// 定义bug宏用于输出调试信息
#define bug(code) do{cout<<"L"<<__LINE__<<":";code;cout<<endl;}while(0)
#else
// 如果没有定义debug宏,定义空的bug宏
#define bug(code) do{}while(0)
#endif
#include<bits/stdc++.h> // 引入所有标准库
using namespace std;
#define int long long // 将int定义为long long类型
#define endl '\n' // 将endl定义为换行符
// 定义for循环宏,便于书写循环
#define _(i,a,b,k) for(int i=a;i<=b;i+=k)
#define _9(i,a,b,k) for(int i=a;i>=b;i-=k)
int n, m; // 定义全局变量n和m
vector<int> arr; // 定义数组arr用于存储数列
vector<int> diff1; // 定义数组diff1用于存储差分数组
vector<int> diff2; // 定义数组diff2用于存储加权差分数组
// 定义操作结构体
struct node {
int op, x, y, k;
};
vector<node> readd; // 定义操作数组
// 定义树状数组类
class Tree {
public:
vector<int> tree; // 定义树状数组
// 构造函数,使用数组初始化树状数组
Tree(vector<int> arr) {
tree.assign(n + 5, 0); // 初始化树状数组
for (int i = 1; i <= n; i++) {
add(i, arr[i]); // 将数组值添加到树状数组中
}
}
Tree() {} // 默认构造函数
// 计算最低位的1
int lowbit(int i) {
return i & -i;
}
// 在树状数组中添加值
void add(int i, int k) {
while (i <= n) {
tree[i] += k;
i += lowbit(i);
}
}
// 计算前缀和
int sum(int i) {
int ans = 0;
while (i > 0) {
ans += tree[i];
i -= lowbit(i);
}
return ans;
}
// 计算区间和
int range(int l, int r) {
return sum(r) - sum(l - 1);
}
};
Tree t1; // 定义树状数组t1
Tree t2; // 定义树状数组t2
// 计算前缀和
int sum1(int i) {
return i * t1.sum(i) - t2.sum(i);
}
// 计算区间和
int range1(int l, int r) {
return sum1(r) - sum1(l - 1);
}
// 主解决函数
void solve() {
diff1.assign(n + 5, 0); // 初始化差分数组
diff2.assign(n + 5, 0); // 初始化加权差分数组
_(i, 1, n, 1) {
diff1[i] = arr[i] - arr[i - 1]; // 计算差分数组
diff2[i] = diff1[i] * (i - 1); // 计算加权差分数组
}
t1 = Tree(diff1); // 初始化树状数组t1
t2 = Tree(diff2); // 初始化树状数组t2
for (auto& xx : readd) {
int op = xx.op, x = xx.x, y = xx.y, k = xx.k;
if (op == 1) { // 如果操作是1,进行区间加法
t1.add(x, k);
t1.add(y + 1, -k);
t2.add(x, (x - 1) * k);
t2.add(y + 1, (y + 1 - 1) * (-k));
} else { // 如果操作是2,输出区间和
cout << range1(x, y) << endl;
}
}
}
signed main() {
ios::sync_with_stdio(false); // 关闭同步
cin.tie(0); // 解除cin与cout的绑定
cout.tie(0); // 解除cout与cin的绑定
cin >> n >> m; // 输入n和m
arr.assign(n + 5, 0); // 初始化数组
readd.clear(); // 清空操作数组
_(i, 1, n, 1) {
cin >> arr[i]; // 输入数组值
}
_(i, 1, m, 1) {
int op = 0, x = 0, y = 0, k = 0;
cin >> op; // 输入操作类型
if (op == 1) {
cin >> x >> y >> k; // 输入操作参数
} else {
cin >> x >> y; // 输入操作参数
}
readd.push_back({ op, x, y, k }); // 将操作添加到操作数组
}
solve(); // 调用解决函数
}
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!