P3373 【模板】线段树 2
【模板】线段树 2
如题,已知一个数列,你需要进行下面三种操作:
- 将某区间每一个数乘上 ;
- 将某区间每一个数加上 ;
- 求出某区间每一个数的和。
输入格式
第一行包含三个整数 ,分别表示该数列数字的个数、操作的总个数和模数。
第二行包含 个用空格分隔的整数,其中第 个数字表示数列第 项的初始值。
接下来 行每行包含若干个整数,表示一个操作,具体如下:
操作 : 格式:1 x y k
含义:将区间 内每个数乘上
操作 : 格式:2 x y k
含义:将区间 内每个数加上
操作 : 格式:3 x y
含义:输出区间 内每个数的和对 取模所得的结果
输出格式
输出包含若干行整数,即为所有操作 的结果。
样例输入 #1
5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
样例输出 #1
17
2
提示
【数据范围】
对于 的数据:,。
对于 的数据:$n \le 10^3 q \le 10^4$。
对于 的数据:,。
除样例外,。
(数据已经过加强 _)
样例说明:
故输出应为 、()。
解题思路
那么这道题有些颠覆了我对懒标记的认识,首先 加和乘混在一起 肯定要 注意次序,那我们就以先加再乘为例,原变为,乘上后变为,那可以发现这是一个乘法分配律的过程,那么对于加的懒标记不仅要加上,而且要乘,而对于乘的懒标记则要乘上。
线段树解法
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
int n, m, p;
// 线段树模板
#define LL long long
#define ls u << 1
#define rs u << 1 | 1
#define mid ((l + r) >> 1)
struct Node {
int l, r;
int mu, add;
LL sum;
} tr[N << 2];
void pushup(int u) {
tr[u].sum = (tr[ls].sum + tr[rs].sum) % p;
}
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r, tr[u].mu = 1;
if (l == r) {
cin >> tr[u].sum;
return;
}
build(ls, l, mid), build(rs, mid + 1, r);
pushup(u);
}
void pushdown(int u) {
if (tr[u].add == 0 && tr[u].mu == 1) return; // 默认懒标记
int &mu = tr[u].mu, &add = tr[u].add; // 此时的add懒标记,已经处理过了
tr[ls].sum = ((LL)mu * tr[ls].sum % p + (LL)(tr[ls].r - tr[ls].l + 1) * add % p) % p;
tr[rs].sum = ((LL)mu * tr[rs].sum % p + (LL)(tr[rs].r - tr[rs].l + 1) * add % p) % p;
tr[ls].mu = (LL)tr[ls].mu * mu % p;
tr[rs].mu = (LL)tr[rs].mu * mu % p;
tr[ls].add = ((LL)tr[ls].add * mu % p + add) % p;
tr[rs].add = ((LL)tr[rs].add * mu % p + add) % p;
mu = 1, add = 0; // 清空懒标记
}
void add(int u, int L, int R, int v) {
int l = tr[u].l, r = tr[u].r;
if (l >= L && r <= R) {
tr[u].add = ((LL)tr[u].add + v) % p;
tr[u].sum = ((LL)tr[u].sum + v * ((LL)tr[u].r - tr[u].l + 1) % p) % p;
return;
}
if (l > R || r < L) return;
pushdown(u);
add(ls, L, R, v), add(rs, L, R, v);
pushup(u);
}
void mu(int u, int L, int R, int v) {
int l = tr[u].l, r = tr[u].r;
if (l >= L && r <= R) {
tr[u].add = (LL)tr[u].add * v % p; // 比较重要的一步,add要在这里乘上v,因为后面可能要加其他的数而那些数其实是不用乘k的
tr[u].mu = (LL)tr[u].mu * v % p;
tr[u].sum = (LL)tr[u].sum * v % p;
return;
}
if (l > R || r < L) return;
pushdown(u);
mu(ls, L, R, v), mu(rs, L, R, v);
pushup(u);
}
LL query(int u, int L, int R) {
int l = tr[u].l, r = tr[u].r;
if (l >= L && r <= R) return tr[u].sum;
if (l > R || r < L) return 0;
pushdown(u);
return (query(ls, L, R) + query(rs, L, R)) % p;
}
signed main() {
// 文件输入输出
#ifndef ONLINE_JUDGE
freopen("P3373.in", "r", stdin);
#endif
// 加快读入
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> m >> p;
build(1, 1, n);
while (m--) {
int op, l, r;
cin >> op >> l >> r;
if (op == 1) {
int k;
cin >> k;
mu(1, l, r, k);
} else if (op == 2) {
int k;
cin >> k;
add(1, l, r, k);
} else
printf("%lld\n", query(1, l, r));
}
return 0;
}
动态开点线段树解法
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
int n, m, p;
// 动态开点线段树
#define LL long long
#define ls tr[u].l // u节点的左子节点号
#define rs tr[u].r // u节点的右儿子节点号
#define mid ((l + r) >> 1)
struct Node {
int l, r;
// 注意:这里的[l,r]与普通线段树不同!!普通线段树中是控制的范围,动态开点线段树是左右儿子的节点号!
// 动态开点线段树中u节点的控制范围是通过函数的2,3两个参数传递过去的,不是记录在tr[u].l,tr[u].r中的!记录在tr[u].l,tr[u].r中的是左右儿子的节点号!
// 理解清楚这一点非常重要,因为后面在求区间长度是,普通线段树len(u)=(tr[u].r-tr[u].l+1),而动态开点线段树len(u)=(r-l+1),len(ls)=mid-l+1,len(rs)=r-mid
int mu, add; // 乘法标记,加法标记
LL sum; // 区间和
} tr[N << 1];
int root, idx;
// 汇总
void pushup(int u) {
tr[u].sum = (tr[ls].sum + tr[rs].sum) % p;
}
// 创建节点:节点号分配,懒标记初始化
void build(int &u) {
if (u) return;
u = ++idx;
tr[u].add = 0;
tr[u].mu = 1;
}
void pushdown(int &u, int l, int r) {
if (tr[u].add == 0 && tr[u].mu == 1) return; // 默认懒标记
build(ls), build(rs); // 左儿子创建, 右儿子创建
int &mu = tr[u].mu, &add = tr[u].add; // 此时的add懒标记,已经处理过了
tr[ls].sum = ((LL)mu * tr[ls].sum % p + (LL)(mid - l + 1) * add % p) % p;
tr[rs].sum = ((LL)mu * tr[rs].sum % p + (LL)(r - mid) * add % p) % p;
tr[ls].mu = (LL)tr[ls].mu * mu % p;
tr[rs].mu = (LL)tr[rs].mu * mu % p;
tr[ls].add = ((LL)tr[ls].add * mu % p + add) % p;
tr[rs].add = ((LL)tr[rs].add * mu % p + add) % p;
mu = 1, add = 0; // 清空懒标记
}
// 乘法
void mu(int &u, int l, int r, int L, int R, int v) {
build(u); // 动态开点
if (l >= L && r <= R) { // 如果区间被完整覆盖
tr[u].add = (LL)tr[u].add * v % p; // 比较重要的一步,add要在这里乘上v,因为后面可能要加其他的数而那些数其实是不用乘k的
tr[u].mu = (LL)tr[u].mu * v % p;
tr[u].sum = (LL)tr[u].sum * v % p;
return;
}
if (l > R || r < L) return; // 如果没有交集
// 下传懒标记
pushdown(u, l, r);
// 分裂
mu(ls, l, mid, L, R, v), mu(rs, mid + 1, r, L, R, v);
// 汇总
pushup(u);
}
// 加法
void add(int &u, int l, int r, int L, int R, int v) {
build(u); // 动态开点
if (l >= L && r <= R) { // 如果区间被完整覆盖
tr[u].add = ((LL)tr[u].add + v) % p;
tr[u].sum = ((LL)tr[u].sum + ((LL)v * ((LL)r - l + 1)) % p) % p;
return;
}
if (l > R || r < L) return; // 如果没有交集
// 下传懒标记
pushdown(u, l, r);
// 分裂
add(ls, l, mid, L, R, v), add(rs, mid + 1, r, L, R, v);
// 汇总
pushup(u);
}
// 区间查询
LL query(int u, int l, int r, int L, int R) {
if (l >= L && r <= R) return tr[u].sum; // 如果完整命中,返回我的全部
if (l > R || r < L) return 0; // 如果与我无关,返回0
pushdown(u, l, r);
return (query(ls, l, mid, L, R) % p + query(rs, mid + 1, r, L, R) % p) % p;
}
int main() {
// 文件输入输出
#ifndef ONLINE_JUDGE
freopen("P3373_2.in", "r", stdin);
#endif
// 加快读入
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> m >> p;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
add(root, 1, n, i, i, x);
}
while (m--) {
int op, l, r;
cin >> op >> l >> r;
if (op == 1) {
int k;
cin >> k;
mu(root, 1, n, l, r, k);
} else if (op == 2) {
int k;
cin >> k;
add(root, 1, n, l, r, k);
} else
printf("%lld\n", query(root, 1, n, l, r));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
2019-08-29 CentOS Linux更改MySQL数据库目录位置
2018-08-29 黄海下一步要完成的工作
2018-08-29 视频H265格式压缩,软件压缩方法,硬件的没有条件,没法测试。
2018-08-29 Centos下的IO监控与分析
2018-08-29 查看进程运行时间长度
2017-08-29 赤峰项目Nginx进程异常的处理办法