Files
python/TangDou/LuoGuBook/CF438D.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

123 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[题目传送门](https://www.luogu.com.cn/problem/CF438D)
### 一、题目大意
* 操作$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$是可以过这道题的。
### 三、实现代码
```c++
#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;
}
```