惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Last Watchdog
The Last Watchdog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
Y
Y Combinator Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The GitHub Blog
The GitHub Blog
博客园_首页
小众软件
小众软件
I
InfoQ
J
Java Code Geeks
月光博客
月光博客
S
Secure Thoughts
Microsoft Security Blog
Microsoft Security Blog
V
Visual Studio Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Stack Overflow Blog
Stack Overflow Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
N
News and Events Feed by Topic
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Cloudflare Blog
T
Threat Research - Cisco Blogs
A
About on SuperTechFans
H
Help Net Security
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
人人都是产品经理
人人都是产品经理
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Latest news
Latest news
G
GRAHAM CLULEY
IT之家
IT之家
C
Cisco Blogs
Last Week in AI
Last Week in AI
Engineering at Meta
Engineering at Meta
L
LangChain Blog
The Register - Security
The Register - Security
SecWiki News
SecWiki News
M
MIT News - Artificial intelligence
NISL@THU
NISL@THU
T
Tenable Blog
博客园 - Franky
美团技术团队
I
Intezer
U
Unit 42
雷峰网
雷峰网
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
SegmentFault 最新的问题
C
Cyber Attacks, Cyber Crime and Cyber Security

博客园 - 糖豆爸爸

【树上DP前导知识汇总】 快速幂、龟速乘总结 反向建图+拓扑排序 卡特兰数专题(Catalan) AcWing 126. 最大的和 AcWing 431. 守望者的逃离 AcWing 414. 数字游戏 AcWing 468. 魔法阵 AcWing 463. 求和 洛谷 P1632 点的移动 P1056 NOIP2008 普及组 排座椅 洛谷 P1889 士兵站队 洛谷 P1862 输油管道问题 CF444C DZY Loves Colors P2253 好一个一中腰鼓! SCOI2010 P2572 序列操作 P4344 SHOI2015 脑洞治疗仪 T125847 【模板】动态开点线段树 Physical Education Lessons
P3373 【模板】线段树 2
糖豆爸爸 · 2023-08-29 · via 博客园 - 糖豆爸爸

【模板】线段树 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\)

(数据已经过加强 _

样例说明:

故输出应为 \(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;
}