首页 > 其他分享 >P3373 【模板】线段树 2

P3373 【模板】线段树 2

时间:2023-10-23 12:01:43浏览次数:28  
标签:rs int 线段 tr mu add ls P3373 模板

【模板】线段树 2

如题,已知一个数列,你需要进行下面三种操作:

  • 将某区间每一个数乘上 \(x\);
  • 将某区间每一个数加上 \(x\);
  • 求出某区间每一个数的和。

输入格式

第一行包含三个整数 \(n,q,m\),分别表示该数列数字的个数、操作的总个数和模数。

第二行包含 \(n\) 个用空格分隔的整数,其中第 \(i\) 个数字表示数列第 \(i\)

接下来 \(q\)

操作 \(1\): 格式:1 x y k 含义:将区间 \([x,y]\) 内每个数乘上 \(k\)

操作 \(2\): 格式:2 x y k 含义:将区间 \([x,y]\) 内每个数加上 \(k\)

操作 \(3\): 格式:3 x y 含义:输出区间 \([x,y]\) 内每个数的和对 \(m\)

输出格式

输出包含若干行整数,即为所有操作 \(3\)

样例输入 #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

提示

【数据范围】

对于 \(30\%\) 的数据:\(n \le 8\),\(q \le 10\)。
对于 \(70\%\) 的数据:$n \le 10^3 \(,\)q \le 10^4$。
对于 \(100\%\) 的数据:\(1 \le n \le 10^5\),\(1 \le q \le 10^5\)。

除样例外,\(m = 571373\)。

(数据已经过加强 _

样例说明:

P3373 【模板】线段树 2_线段树

故输出应为 \(17\)、\(2\)(\(40 \bmod 38 = 2\))。

解题思路

那么这道题有些颠覆了我对懒标记的认识,首先 加和乘混在一起 肯定要 注意次序,那我们就以先加再乘为例,原\(a_1,a_2,a_3,\dots,a_n\)变为\(a_1+t,a_2+t,a_3+t,\dots,a_n+t\),乘上\(x\)后变为\(a_1x+tx,a_2x+tx,a_3x+tx,\dots,a_nx+tx\),那可以发现这是一个乘法分配律的过程,那么对于加的懒标记不仅要加上\(t\),而且要乘\(x\),而对于乘的懒标记则要乘上\(x\)。

线段树解法

#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;
}



标签:rs,int,线段,tr,mu,add,ls,P3373,模板
From: https://blog.51cto.com/u_3044148/7985504

相关文章

  • T125847 【模板】动态开点线段树
    \(T125847\)题目背景注意:请注意时间限制是800ms请使用较快的输入输出注意:空间限制是128MB请不要开longlong时限在std的2.5倍以上题目描述有一个有\(1000000000\)个数的初始值全为\(0\)的区间,你要进行两种操作:将某区间每一个数加上 \(x\)求出某区间每一个数的和输入格式第一行包......
  • laravel:blade模板(10.27.0)
    一,相关文档:https://learnku.com/docs/laravel/10.x/blade/14852二,创建controller和view1,创建controllerliuhongdi@lhdpc:/data/laravel/dignews$phpartisanmake:controllerCommentController   INFO  Controller[app/Http/Controllers/CommentController.php......
  • KMP模板(洛谷P3375)
    1#include<bits/stdc++.h>2usingnamespacestd;3strings1,s2;4vector<int>find_next(vector<int>next,strings)5{6inti=1,prefix=0,len=s.length();7while(i<len)8{9if(s[prefix]=......
  • 设计模式05 —— 模板模式
    设计模式05——模板模式本教程参考:菜鸟教程-学的不仅是技术,更是梦想!(runoob.com)参考书:《图解设计模式》本系列为本人学习笔记,和课程学习笔记,资料和参考均源自互联网,希望各位大佬多多指点!介绍在模板模式(TemplatePattern)中,一个抽象类公开定义了执行它的方法的方式/模......
  • 从零用VitePress搭建博客教程(5) - 如何自定义页面模板、给页面添加独有的className和
    接上一节:从零用VitePress搭建博客教程(4)–如何自定义首页布局和主题样式修改?上一节其实我们也简单说了自定义页面模板,这一节更加详细一点说明,开始之前我们要知道在vitePress中,.md的文件是可以直接编写vue的代码的。比如我们现在来自定义一个前端网址导航页面八、自定义一些......
  • 使用axum构建博客系统 - 模板
    我们的博客分为“前台”和“后台”两部分。前台用于展示博客内容,后台用于管理博客。本章我们将编写前台和后台的基础模板以及对应的路由。目录结构前台模板位于 templates/frontend,后台模板位于templates/backend。前台我们的前台模板基于 Bootstrap的Blog 修改而来布局......
  • 使用axum构建博客系统 - 后台管理菜单及首页模板
    目前,后台管理功能基本完成,但还有两个工作没做:清理后台管理的导航菜单以及后台管理首页的模板。后台管理菜单<!--templates/backend/base.html--><!--...--><divclass="container-fluid"><divclass="row"><navid="sidebarMenu"c......
  • 巧用模板字符串将未知变量转换为string类型,避免报错
    可理解为将变量向字符串类型转换的语法糖用法我们通常会遇到需要用String.prototype上的方法处理变量,如果该变量为null、undefined、Object则不能直接用字符串方法,也不易于统一处理为字符串;使用模板字符串包裹该变量,则可以简单粗暴的将任意类型转换为字符串类型,避免报错。案例:......
  • STL(标准模板库)
    以下是关于STL(标准模板库)的一个详细复习提纲,以帮助你温习相关知识点。序列容器vector定义和创建vectorvector的常用操作方法(例如插入、删除、访问元素等)vector的动态扩容机制vector的迭代器使用list定义和创建listlist的常用操作方法(例如插入、删除、访问元素等)list......
  • 快速排序算法模板+内置函数
    思想:确定分界点调整区间,小于分界点的在左边区间,大于分界点在右边区间。递归处理左右两边。voidquick_sort(intq[],intl,intr){if(l>=r)return;inti=l-1,j=r+1,x=q[l+r>>1];while(i<j){doi++;while(q[i......