You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

题目传送门

一、题目大意

  • 操作1 给定区间[l,r],对序列中这个区间中每个数字累加求和。

  • 操作2 给定区间[l,r]x,对区间每个数字对x取模。

  • 操作3

二、思路

注意到m = 1e5,所以整体时间复杂度O(nlog n),也就是说你的所有操作时间复杂度不超过O(log n)才过通过这个题。

注意到区间求和,单点操作用线段树都可以在O(log n)做到,唯一有难度的就是操作对区间所有数取模。

首先考虑一个小小的剪枝,如果某个区间里面的最大数都<x,那么这个区间不用管了,也就是说对于区间内的每个数x,对它取模,这个数至少会减低x/2,那么我们每次对这个数进行取模,这个数到0的时间复杂度也无非就是O(logx),所以整体时间复杂度O(mlogmlogx),带两个log是可以过这道题的。

三、实现代码

#include <bits/stdc++.h>
using namespace std;

//宏定义左右儿子
#define ls u << 1
#define rs (u << 1) | 1

int n, m;
typedef long long LL;
const int N = 1e5 + 10;
int a[N];

struct Node {
    int l, r;
    LL sum, max;
} tr[N << 2];

void pushup(int u) {
    tr[u].sum = tr[ls].sum + tr[rs].sum;     //更新父节点的区间和
    tr[u].max = max(tr[ls].max, tr[rs].max); //更新父节点的区间最大值
}
void build(int u, int l, int r) {
    tr[u] = {l, r};
    if (l == r) {
        //要重视这个初始值赋值!!!
        //注意:这里是a[l]或a[r],可不是a[u],u是在线段树中的节点号与原数字是不直接相关的是辅助性的东西.
        //而l,r在叶子节点时l=r,比如[2,2],其实就是第2个输入的值a[2]
        tr[u].max = tr[u].sum = a[l]; //叶子的话最大值区间和都是一个即a[l]
        return;
    }
    int mid = (l + r) >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);

    //因为有初始值赋值操作,需要向父节点汇集信息
    pushup(u);
}
//单点修改
void modify(int u, int x, int v) {
    //不在管理范围的修改直接返回
    if (tr[u].l > x || tr[u].r < x) return;
    //叶子节点命中
    if (tr[u].l == tr[u].r) {
        tr[u].sum = tr[u].max = v;
        return;
    }
    //不管在左还是在右,全都进行修改,递推函数第一句会把不对的位置剔除掉
    modify(ls, x, v), modify(rs, x, v);
    //子节点信息修改,需要更新父节点信息
    pushup(u);
}

//区间取模
void modify(int u, int l, int r, int x) {
    //不在管理范围的修改直接返回
    if (tr[u].l > r || tr[u].r < l) return;
    if (tr[u].max < x) return; //减枝 最大值都比x小取一遍模的话原来的数字也不能变
    if (tr[u].l == tr[u].r) {
        tr[u].sum %= x;        //暴力取模每个叶子节点对x取模
        tr[u].max = tr[u].sum; //最大值肯定也变小了,因为是叶子节点,最大值就是本身
        return;
    }
    //左改改,右改改
    modify(ls, l, r, x), modify(rs, l, r, x);
    //区间改完,需要向父节点推送统计信息
    pushup(u);
}

//查询区间和
LL query(int u, int l, int r) {
    //不在管理范围的修改直接返回
    if (tr[u].l > r || tr[u].r < l) return 0;
    //区间完全命中,返回结果
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
    //返回左右子树的查询结果
    return query(ls, l, r) + query(rs, l, r);
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    //构建线段树
    build(1, 1, n);

    int l, r, k, x;
    while (m--) {
        int op;
        scanf("%d", &op);
        if (op == 1) {
            scanf("%d%d", &l, &r);
            printf("%lld\n", query(1, l, r)); //查询区间和
        }
        if (op == 2) {
            scanf("%d%d%d", &l, &r, &x);
            modify(1, l, r, x); // 区间中每个数字 % x
        }
        if (op == 3) {
            scanf("%d%d", &k, &x);
            modify(1, k, x); //单点修改第k个位置值为x
        }
    }
    return 0;
}