线段树求调 (代码里面有注释)
查看原帖
线段树求调 (代码里面有注释)
492676
梦回江南楼主2022/10/11 23:06

其实只要看section_query(区间查询)和node_modify(单点修改)这两个函数就好.我习惯用了结构体.

#include <iostream>
#include <algorithm>
#include <iomanip>
#include <memory.h>

#define endl '\n'
#define ref(i, a, b, p) for (signed(i) = (a); (i) <= signed(b); (i) += signed(p))
#define gef(i, a, b, p) for (signed(i) = (a); (i) >= signed(b); (i) -= signed(p))
#define LL long long
using namespace std;

const int maxn = 5e5 + 5;
int n, m;
int a[maxn];

struct common_segment_tree // 普通线段树
{
    struct tree // 节点
    {
        int l, r, num, lz; // 左端点, 右端点, 值

        // tree() {}                                     // 空构造函数
        // tree(const int l, const int r, const int num) // 赋值构造函数
        // {
        //     this->l = l, this->r = r, this->num = num;
        // }
    };
    tree tr[maxn * 40];

    void build(int i, int l, int r) // 建立线段树, i 为当前节点, l 为区间左端点, r 为区间右端点
    {
        tr[i].l = l, tr[i].r = r;       // 初始化
        if (l == r)        // 如果是叶子节点, 那么赋值然后返回
        {
            tr[i].num = a[l];
            return;
        }
        int mid = (l + r) >> 1;        // 不是叶子结点, 那么接着分叉
        build(i << 1, l, mid);         // 左子树
        build(i << 1 | 1, mid + 1, r); // 右子树
        return;
    }

    void push_down(int i) // 向下传递 "懒" 标记, i 为当前节点
    {
        if (tr[i].lz != 0)
        {
            tr[i << 1].lz += tr[i].lz;
            tr[i << 1 | 1].lz += tr[i].lz;
            int mid = (tr[i].l + tr[i].r) >> 1;
            tr[i << 1].num += tr[i].lz * (mid - tr[i << 1].l + 1);
            tr[i << 1 | 1].num += tr[i].lz * (tr[i << 1 | 1].r - mid);
            tr[i].lz = 0;
        }
        return;
    }

    void node_modify(int i, int x, int k) // 单点修改, i 为当前节点, x 为待修改节点下标, k 为待修改值
    {
        if (tr[i].l == tr[i].r) //  叶子结点说明已经找到, 加上值直接返回
        {
            tr[i].num += k;
            return;
        }
        if (x <= tr[i].r) // 在左子树
            node_modify(i << 1, x, k);
        else // 在右子树
            node_modify(i << 1 | 1, x, k);
        tr[i].num = tr[i << 1].num + tr[i << 1 | 1].num; // 维护当前节点的值
        return;
    }

    int section_query(int i, int l, int r) // 区间查询, i 为当前节点, l 为区间左端点, r 为区间右端点
    {
        if (tr[i].l >= l && tr[i].r <= r) // 表示这个区间被所查询区间包含, 则直接返回这个区间的值
            return tr[i].num;
        if (tr[i].r < l || tr[i].l > r) // 表示这个区间与所查询区间的交集为空集, 则直接返回 0
            return 0;
        push_down(i); // 向下传递 "懒" 标记
        int ans = 0, mid = (l + r) >> 1;
        if (mid >= l) // 左子树与所查询区间有交集, 递归查询
            ans += section_query(i << 1, l, r);
        if (tr[i << 1 | 1].l <= r) // 右子树与所查询区间有交集, 递归查询
            ans += section_query(i << 1 | 1, l, r);
        return ans;
    }

    void section_modify(int i, int k, int l, int r) // 区间修改, i为当前节点, k 为需要修改的值, l 为区间左端点, r 为区间右端点
    {
        if (tr[i].l >= l && tr[i].r <= r) // 表示这个区间在要修改的范围内,那么修改完返回
        {
            tr[i].num += k * (tr[i].r - tr[i].l + 1);
            tr[i].lz += k;
            return;
        }
        push_down(i);                                     // 如果不在区间内
        if (tr[i << 1].r >= l)                           // 左区间与要修改范围有交集
            section_modify(i << 1, k, l, r);             // 在此区间的左子树内查找
        if (tr[i << 1 | 1].l <= r)                       // 有区间与要修改范围有交集
            section_modify(i << 1 | 1, k, l, r);         // 在此区间的右子树内查找
        tr[i].num = tr[i << 1].num + tr[i << 1 | 1].num; // 维护当前节点的值
        return;
    }

    int node_query(int i, int k, int ans) // 单点查询, i 为当前节点, k 为要查找的数的下标, ans 为值
    {
        ans += tr[i].num;       // 找到一个点就相加
        if (tr[i].l == tr[i].r) // 如果是叶子节点就返回
            return ans;
        int mid = (tr[i].l + tr[i].r) >> 1; // 不是叶子节点就继续分叉
        if (k <= mid)                       // 值在左子树
            return node_query(i << 1, k, ans);
        else // 值在右子树
            return node_query(i << 1 | 1, k, ans);
    }

};

// struct

common_segment_tree cst;

void simulation_common_segment_tree() // 调试普通线段树, 洛谷 P3368 P3374, 已 AC. 普通线段树板子题
{
    cin >> n >> m;  // n 为集合大小, m 为操作次数
    ref(i, 1, n, 1) // 输入
        cin >>
        a[i];

    cst.build(1, 1, n); // 建立线段树

    ref(i, 1, m, 1)
    {
        int opr = 0;
        cin >> opr;
        // if (opr == 1) // opr = 1 为区间修改
        // {
        //     int x = 0, y = 0, k = 0;
        //     cin >> x >> y >> k; // k 为修改值, x 为区间左端点, y 为区间右端点
        //     cst.section_modify(1, k, x, y);
        // }
        // else if (opr == 2) // opr = 2 为单点查询
        // {
        //     int x = 0;
        //     cin >> x; // x 为查询数的下标
        //     cout << cst.node_query(1, x, 0) << endl;
        // }
        if (opr == 1) // opr = 3 为单点修改
        {
            int x = 0, k = 0;
            cin >> x >> k; // x 为查询数的下标, k 为待修改的值
            cst.node_modify(1, x, k);
        }
        else if (opr == 2) // opr = 4 为区间查询
        {
            int x = 0, y = 0;
            cin >> x >> y; // x 为欲查询区间左端点, y 为欲查询区间右端点
            cout << cst.section_query(1, x, y) << endl;
        }
    }

    return;
}

void work()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    simulation_common_segment_tree();

    return;
}

int main()
{
    work();

    return 0;
}
2022/10/11 23:06
加载中...