【一百零七】【算法分析与设计】P3374 【模板】树状数组 1,P3368 【模板】树状数组 2,P3372 【模板】线段树 1,树状数组实现单点更新范围查询,范围更新单点查询,范围更新范围查询

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 1n8 1 ≤ m ≤ 10 1\le m \le 10 1m10; 对于 70 % 70\% 70% 的数据, 1 ≤ n , m ≤ 1 0 4 1\le n,m \le 10^4 1n,m104; 对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 5 × 1 0 5 1\le n,m \le 5\times 10^5 1n,m5×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

题目描述

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

  1. 将某区间每一个数加上 x x x

  2. 求出某一个数的值。

输入格式

第一行包含两个整数 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 N8 M ≤ 10 M\le10 M10

对于 70 % 70\% 70% 的数据: N ≤ 10000 N\le 10000 N10000 M ≤ 10000 M\le10000 M10000

对于 100 % 100\% 100% 的数据: 1 ≤ N , M ≤ 500000 1 \leq N, M\le 500000 1N,M500000 1 ≤ x , y ≤ n 1 \leq x, y \leq n 1x,yn,保证任意时刻序列中任意元素的绝对值都不大于 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

题目描述

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

  1. 将某区间每一个数加上 k k k
  2. 求出某区间每一个数的和。

输入格式

第一行包含两个整数 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. 1 x y k:将区间 [ x , y ] [x, y] [x,y] 内每个数加上 k k k
  2. 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 n8 m ≤ 10 m \le 10 m10。 对于 70 % 70\% 70% 的数据: n ≤ 10 3 n \le {10}^3 n103 m ≤ 10 4 m \le {10}^4 m104。 对于 100 % 100\% 100% 的数据: 1 ≤ n , m ≤ 10 5 1 \le n, m \le {10}^5 1n,m105

保证任意时刻数列中所有元素的绝对值之和 ≤ 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(); // 调用解决函数
}

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值