P2357 守墓人

守墓人问题 - 解题思路与代码注释

解题思路

这道题目需要使用线段树来高效处理区间更新和查询操作。线段树能够在O(logN)时间内完成区间增减和区间求和操作,非常适合处理大规模数据。

主要操作包括:

  1. 区间增减(操作1)

  2. 主墓碑单独增减(操作2、3)

  3. 区间查询(操作4)

  4. 主墓碑查询(操作5)

由于墓碑数量可能非常大(2×10^5),线段树是最合适的数据结构。主墓碑(1号)可以看作是线段树中最左边的叶子节点。

代码注释

#include<bits/stdc++.h>
#define lc rt << 1      // 左子节点索引
#define rc rt << 1 | 1  // 右子节点索引
#define lson lc,l,mid   // 左子树参数
#define rson rc,mid + 1,r // 右子树参数
#define ll long long    // 定义长整型别名
using namespace std;

const int N = 2e5 + 10, inf = 0x3f3f3f3f;

// 线段树节点结构体
struct node{
    ll sum;    // 区间和
    ll lazy;   // 懒惰标记,用于延迟更新
};

node t[N << 2]; // 线段树数组,大小是4倍原始数据量
ll n, m, a[N];  // n-墓碑数,m-操作数,a-初始风水值数组

// 向上更新节点信息
void pushup(int rt)
{
    t[rt].sum = t[lc].sum + t[rc].sum; // 当前节点的和等于左右子节点和相加
}

// 向下更新节点信息
void down(int rt, int l, int r, ll z)
{
    t[rt].sum += (r - l + 1) * z; // 更新区间和
    t[rt].lazy += z;              // 更新懒惰标记
}

// 向下传递懒惰标记
void pushdown(int rt, int l, int r)
{
    if(t[rt].lazy == 0) return;    // 如果没有懒惰标记,直接返回
    int mid = (l + r) >> 1;
    down(lson, t[rt].lazy);        // 更新左子树
    down(rson, t[rt].lazy);        // 更新右子树
    t[rt].lazy = 0;                // 清空当前节点的懒惰标记
}

// 构建线段树
void build(int rt, int l, int r)
{
    t[rt].lazy = 0;                // 初始化懒惰标记
    if(l == r){                    // 如果是叶子节点
        t[rt].sum = a[l];         // 直接赋值
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);                   // 构建左子树
    build(rson);                   // 构建右子树
    pushup(rt);                    // 更新当前节点信息
}

// 区间更新操作
void change(int rt, int l, int r, int x, int y, ll z)
{
    if(r < x || y < l) return;     // 完全不在区间内,直接返回
    if(x <= l && r <= y)           // 当前区间完全在更新区间内
    {
        t[rt].sum += (r - l + 1) * z; // 更新区间和
        t[rt].lazy += z;              // 设置懒惰标记
        return;
    }
    pushdown(rt, l, r);            // 向下传递懒惰标记
    int mid = (l + r) >> 1;
    change(lson, x, y, z);         // 更新左子树
    change(rson, x, y, z);         // 更新右子树
    pushup(rt);                    // 向上更新节点信息
}

// 区间查询操作
ll query(int rt, int l, int r, int x, int y)
{
    if(r < x || y < l) return 0;   // 完全不在查询区间内,返回0
    if(x <= l && r <= y) return t[rt].sum; // 当前区间完全在查询区间内,直接返回和
    pushdown(rt, l, r);            // 向下传递懒惰标记
    int mid = (l + r) >> 1;
    return query(lson, x, y) + query(rson, x, y); // 返回左右子树查询结果的和
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) scanf("%lld", &a[i]); // 读取初始风水值
    build(1, 1, n);               // 构建线段树
    
    while(m--)
    {
        int op; scanf("%d", &op);  // 读取操作类型
        if(op == 1){               // 区间增加操作
            ll x, y, z; scanf("%lld%lld%lld", &x, &y, &z);
            change(1, 1, n, x, y, z);
        }
        if(op == 2){               // 主墓碑增加操作
            ll z; scanf("%lld", &z);
            change(1, 1, n, 1, 1, z);
        }
        if(op == 3){               // 主墓碑减少操作
            ll z; scanf("%lld", &z);
            change(1, 1, n, 1, 1, -z);
        }
        if(op == 4){               // 区间查询操作
            ll x, y; scanf("%lld%lld", &x, &y);
            printf("%lld\n", query(1, 1, n, x, y));
        }
        if(op == 5){               // 主墓碑查询操作
            printf("%lld\n", query(1, 1, n, 1, 1));
        }
    }
    return 0;
}

 

posted @ 2025-05-27 14:59  CRt0729  阅读(19)  评论(0)    收藏  举报