线段树历史值学习笔记

news/2025/10/18 22:57:02/文章来源:https://www.cnblogs.com/lajishift/p/19150145

(先单开出来,后面准备合并到线段树 trick 里)
(好像合并不了了)

历史和指的是线段树维护的序列 \(a\),我们再开一个序列 \(b\),每次修改 / 查询后进行 \(\forall b_i \leftarrow b_i + a_i\) 操作,\(b\) 称作 \(a\) 的历史和。

历史和一般搭配扫描线使用,多用于二维的问题模型。

做法

做法一:\(c_i = h_i - t \times a_i\)

最简单好写,也最不易推广的做法。

构造 \(c_i = h_i - t \times a_i\),每次 \(t\) 增长 \(1\)\(h_i \leftarrow h_i + a_i\),但是 \(c_i\)\(a_i\) 不修改的情况下不会变化。
对于 \(a_i \leftarrow a_i + v\)\(c_i \leftarrow c_i - tv\) 即可。

查询历史和,即 \(\sum c_i + t \sum a_i\),维护 \(a,c\) 即可。

做法二:矩阵

据说,矩阵乘法在这种问题中是万能的。

发现难点在于更新历史和是全局更新,但是朴素方法手动更新(维护 \(hsum, sum\)\(hsum \leftarrow hsum + sum\))复杂度不对。
这本质上是因为没有将加法和更新历史和的操作都拼合成一种标记(满足结合律,可以快速合并的标记)。

矩阵乘法及广义乘法满足了我们的需求。

我们可以维护 \(\begin{bmatrix} hsum, sum, len \end{bmatrix}\) 作为线段树节点的信息,并另外维护一个 \(3 \times 3\) 的矩阵标记。

对于区间加 \(v\),要实现 \(sum \leftarrow sum + v \times len\)
区间加矩阵:

\[\begin{bmatrix}1 & 0 & 0\\0 & 1 & 0\\0 & v & 1\\\end{bmatrix} \]

对于更新历史和,要实现 \(hsum \leftarrow hsum + sum\)
更新历史和矩阵:

\[\begin{bmatrix}1 & 0 & 0\\1 & 1 & 0\\0 & 0 & 1\end{bmatrix} \]

区间覆盖矩阵也能做,只需要把区间加矩阵的 \((2,2)\) 位置改成 \(0\),即 \(sum \leftarrow v \times len\) 即可。

弱点在于常数问题,当然可以手动拆开转移,只保留会改变的部分。

做法三:标记队列

部分参考 command_block 的博客

在线段树标记的下推机制中,某个点存有标记,表示整一棵子树在标记存在的时间内都未曾更新。
于是,问题的核心就在于分析单个节点上停留的标记的影响。

在非历史值问题中,我们只关注当下的标记,所以我们永远合并标记,便于存储。
但是在历史值问题中,我们需要考虑历史上存储过的标记的依次作用和当前的合并结果。

为了便于理解,我们暂时不考虑实现的可行性,我们假定每个节点维护了整个标记序列,以时间为顺序。
线段树上的每个节点维护一个类似“标记队列”,队列每一项是形如 \(+v\) 的加法操作或是更新历史和操作。

暂时,我们的线段树节点应该要维护一下信息:区间和 \(sum\),历史和 \(hsum\),加法标记 \(add\)

对于队列里面的操作,会对节点信息造成一下影响。

  • \(+ v\) 操作:\(sum \leftarrow sum + v \times len,add \leftarrow add + v\)
  • 更新历史和操作:\(hsum \leftarrow hsum + sum\)

那么我们考虑将父亲的队列合并到儿子的队列上是怎样的。
请注意,线段树上,父亲的标记合并到儿子的标记上时,儿子标记的时间是更靠前的,这对于不满足交换律的合并运算(如矩阵乘法)是至关重要的。

设将队列 \(2\) 合并到队列 \(1\) 上,队列 \(1\) 的时间靠前。
以下 \(sum, hsum\) 指的是节点存储的信息。\(add\) 是队列中 \(+v\) 操作的合并结果。

  • \(add_1 \leftarrow add_1 + add_2\),直接继承。

  • \(sum_1 \leftarrow sum_1 + add_2\)

  • 考虑 \(hsum_1\) 的变化:

    在加入队列 \(2\) 的若干操作后,原先的 \(sum_1\) 指的是队列 \(1\) 的合并结果,它会在队列 \(2\) 的每个“更新历史和”操作中用到,造成系数为 \(1\) 的贡献。
    \(upd\) 为一个队列中,“更新历史和”操作的次数,则这一部分贡献为 \(sum_1 \times upd_2\)

    还有一部分贡献来自于队列 \(2\) 中的 \(+ v\) 操作,它们会在队列 \(2\) 的每个“更新历史和”操作时作用在 \(hsum\) 上,因为是加法标记,造成系数为 \(len_1\) 的贡献。
    那么我们需要知道一个队列里,「每次“更新历史和”时的 \(add\)」 的和,记为 \(hadd\)
    这部分的贡献为 \(hadd_2 \times len_1\)

    综上,\(hsum_1 \leftarrow hsum_1 + sum_1 \times upd_2 + hadd_2 \times len_1\)

  • \(upd_1 \leftarrow upd_1 + upd_2\)

  • \(hadd_1 \leftarrow hadd_1 + add_1 \times upd_2 + hadd_2\)

    首先 \(hadd_1, hadd_2\) 造成贡献是显然的,都是在各自的队列时间范围内的贡献。

    还有队列 \(1\)\(+v\) 操作的合并结果 \(add_1\) 在队列 \(2\) 的时间范围内造成的贡献,是每一次队列 \(2\) 中“更新历史和”操作时体现的,故系数为 \(upd_2\)

于是,我们发现我们只需要刻画 \(add, upd, hadd\) 即可刻画出一整个队列,加上节点本身的 \(hsum, sum\),维护这些标记即可。

同时你会发现,标记队列不好做区间覆盖,这也是它的局限性。

例题

CF1834D

题解

P8868 [NOIP 2022] 比赛

本题使用标记队列法来解是更简单的。

请阅读并充分理解标记队列法,并充分理解 CF1824D 的扫描线做法,然后阅读此题解。

题意

两个序列 \(a,b\) 长度均为 \(n\)\(q\) 次询问,给出一个区间 \([l,r]\),求:

\[\sum \limits _ {l' = l} ^ r \sum \limits _ {r' = l'} ^ r \left( \max \limits _{i = l'} ^ {r'} a_i \right ) \times \left ( \max \limits _{i = l'} ^ {r'} b_i\right ) \]

人话是子区间的 \(a,b\) 极值的乘积的和。

问题分析

会了 CF1824D 之后,你应该很容易地知道这题应该使用扫描线,并且有能力预见到是扫描线配合线段树历史和的 trick。

离线询问,扫描线右端点,记录 \(f_i\) 表示对于当前右端点,左端点为 \(i\) 时的答案。

我们依旧是考虑右端点 \(j\) 移动时的改变,因为需要维护 \(a,b\) 的最大值,不难想到要维护单调栈(单调递减单调栈)
每次将两个单调栈(分别维护 \(a,b\))中 \(\lt a_j\) 的全部弹出,那么栈顶到 \(j\) 的位置全部更新 \(a\)\(b\)

于是数据结构要实现:

  • \(a\) 区间加,对 \(b\) 区间加(或者看成区间覆盖,但是标记队列不好做区间覆盖,单调栈的性质让我们可以改成区间加)。

  • 查询区间 \(a \times b\) 的历史和。

数据结构

知道了标记队列的做法后,这题就是标记队列进行简单更改后得到的。

线段树维护:

  • \(sab\),表示 \(a \times b\) 的区间和。

  • \(sa, sb\) 分别表示区间 \(a,b\) 的和。

  • \(hsab\),表示 \(sab\) 的历史和。

标记队列应当包括(但不限于):

  • \(adda, addb\),分别表示 \(a, b\) 的加法标记。

  • 更新历史和标记。

其影响:

  • \(a + v\) 操作,\(sab \leftarrow sab + v \times sb, sa \leftarrow sa + v \times len, adda \leftarrow adda + v\)

  • \(b + v\) 操作,\(sab \leftarrow sab + v \times sa, sb \leftarrow sb + v \times len, addb \leftarrow addb + v\)

  • 更新历史和操作:\(hsab \leftarrow hsab + sab\)

合并队列,依旧是队列 \(2\) 合并到队列 \(1\)

以下是定义:

    ull hsab, // sum a * b 的历史和sab, // sum a * bsa, // sumasb, // sumblen, // 区间长度hab, // a * b 每次操作的历史和ha, // a 每次操作历史和hb, // b 每次操作历史和upd, // 更新历史和操作次数adda, // a 加法标记addb; // b 加法标记

转移:完全就是板子题式子的稍微变种,只是注意分 \(a,b\) 讨论即可。

\[\begin{aligned} hab_1 &\leftarrow hab_1 + adda_1 \times addb_1 \times upd_2 + adda_1 \times hb_2 + addb_1 \times ha_2 + hab_2, \\[6pt]ha_1 &\leftarrow ha_1 + adda_1 \times upd_2 + ha_2, \\[6pt]hb_1 &\leftarrow hb_1 + addb_1 \times upd_2 + hb_2, \\[6pt]sab_1 &\leftarrow sab_1 + sa_1 \times addb_2 + sb_1 \times adda_2 + addb_2 \times adda_2 \times len_1, \\[6pt]sa_1 &\leftarrow sa_1 + adda_2 \times len_1, \\[6pt]sb_1 &\leftarrow sb_1 + addb_2 \times len_1, \\[6pt]upd_1 &\leftarrow upd_1 + upd_2, \\[6pt]adda_1 &\leftarrow adda_1 + adda_2, \\[6pt]addb_1 &\leftarrow addb_1 + addb_2. \end{aligned} \]

代码

const int N = 3e5 + 5;
int n, q;
ull a[N], b[N], ans[N];struct node{// ull adda,addb,upd,ha,hb,l;ull hsab, // sum a * b 的历史和sab, // sum a * bsa, // sumasb, // sumblen, // 区间长度hab, // a * b 每次操作的历史和ha, // a 每次操作历史和hb, // b 每次操作历史和upd, // 更新历史和操作次数adda, // a 加法标记addb; // b 加法标记node(){hsab = sab = sa = sb = len = hab = ha = hb = upd = adda = addb = 0;}
} t[N << 2];node calc_add_node(bool type, ull v, int len){node res;if(type == 0) res.adda = v;else res.addb = v;res.len = len;return res;}node upd_h_node;#define mid ((l + r) >> 1)
#define ls(x) (x << 1)
#define rs(x) ((x << 1) | 1)
void push_up(int x){t[x].sab = t[ls(x)].sab + t[rs(x)].sab;t[x].sa = t[ls(x)].sa + t[rs(x)].sa;t[x].sb = t[ls(x)].sb + t[rs(x)].sb;t[x].hsab = t[ls(x)].hsab + t[rs(x)].hsab;
}
void hard(int x, node v){t[x].hsab += t[x].sab * v.upd + t[x].sa * v.hb + t[x].sb * v.ha + v.hab * t[x].len;t[x].hab += t[x].adda * t[x].addb * v.upd + t[x].adda * v.hb + t[x].addb * v.ha + v.hab;t[x].ha += t[x].adda * v.upd + v.ha;t[x].hb += t[x].addb * v.upd + v.hb;t[x].sab += t[x].sa * v.addb + t[x].sb * v.adda + v.addb * v.adda * t[x].len;t[x].sa += v.adda * t[x].len;t[x].sb += v.addb * t[x].len;t[x].upd += v.upd;t[x].adda += v.adda;t[x].addb += v.addb;
}
void push_down(int x){hard(ls(x), t[x]);hard(rs(x), t[x]);t[x].hab = t[x].ha = t[x].hb = t[x].upd = t[x].adda = t[x].addb = 0;
}
void build(int x, int l, int r){t[x].len = r - l + 1;if(l == r) return;build(ls(x), l, mid);build(rs(x), mid + 1, r);push_up(x);
}
void modify(int x, int l, int r, int ql, int qr, ull v, bool type){ // type : 0 -> a , 1 -> bif(ql <= l && r <= qr){hard(x, calc_add_node(type, v, r - l + 1));return;}push_down(x);if(ql <= mid) modify(ls(x), l, mid, ql, qr, v, type);if(qr > mid) modify(rs(x), mid + 1, r, ql, qr, v, type);push_up(x);
}
ull query(int x, int l, int r, int ql, int qr){if(ql <= l && r <= qr){return t[x].hsab;}push_down(x);ull res = 0;if(ql <= mid) res += query(ls(x), l, mid, ql, qr);if(qr > mid) res += query(rs(x), mid + 1, r, ql, qr);return res;
}struct Query{int l, qid;
};
vector<Query> qry[N];int stk_a[N], stk_b[N], top_a, top_b;
void solve_test_case(){int cid = read();n = read();upd_h_node.upd = 1;rep(i, 1, n) a[i] = read();rep(i, 1, n) b[i] = read();q = read();rep(i, 1, q){int l = read(), r = read();qry[r].push_back({l, i});}build(1, 1, n);top_a = top_b = 1;// stk_a[++top_a] = 0, stk_b[++top_b] = 0;a[0] = b[0] = n + 1;rep(i, 1, n){while(a[stk_a[top_a]] < a[i]){modify(1, 1, n, stk_a[top_a - 1] + 1, stk_a[top_a], -a[stk_a[top_a]], 0);top_a--;}   modify(1, 1, n, stk_a[top_a] + 1, i, a[i], 0);stk_a[++top_a] = i;while(b[stk_b[top_b]] < b[i]){modify(1, 1, n, stk_b[top_b - 1] + 1, stk_b[top_b], -b[stk_b[top_b]], 1);top_b--;}modify(1, 1, n, stk_b[top_b] + 1, i, b[i], 1);stk_b[++top_b] = i;hard(1, upd_h_node);for(auto [l, qid] : qry[i]){ans[qid] = query(1, 1, n, l, i);}}rep(i, 1, q){write(ans[i]);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/939815.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

连续两行fastq、连续两行MD5值如何转换为每行一个fastq一个MD5格式

001、shell实现(base) [b20223040323@admin2 test]$ ls a.txt (base) [b20223040323@admin2 test]$ cat a.txt ## 测试数据 SRR5534377_1.fastq.gz SRR5534377_2.fastq.gz d27d0b0f0bb9cae5dc52dc934384699b 1139…

bridge 一般是 网络桥接模块。

bridge 一般是 网络桥接模块。bridge 一般是 网络桥接模块。 在 Linux 网络栈中,Bridge 就是用来实现 “二层转发(L2 switch)” 的:让两个网络接口互通(比如 eth0 ↔ wlan0);常用于路由器的 AP 模式;也可能是 …

深入解析密码库低级lowlevel抽象层接口与高级highlevel抽象层接口 - 实践

深入解析密码库低级lowlevel抽象层接口与高级highlevel抽象层接口 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family:…

abc428

AC 4 (ABCE), Score 1125, Penalty 42:25(1), Rank , Ranting .4 题遗憾离场/ll C 删字符的时候忘记删字符的,罚时 +1 D 怎么这么困难,跳了。 E 大水题换根 dp 板子。 罚坐 1h,F 以为自己会了的时候发现读错题了,纠…

周六训练-1018

C Cut 我的质因数分解不是在杨老师这里学的,所以我写挂了,杨老师好闪拜谢杨老师 #include <iostream> #define int long longusing namespace std;const int MaxN = 1e5 + 10;int f[MaxN][20], mn[MaxN][20], …

23-网关选型

网关选型指南:支持多协议的统一流量入口摘要:本文档全面分析了支持HTTP/1.1、WebSocket、gRPC和gRPC-Web协议的统一网关选型方案,通过对比Nginx、Envoy、Kong、APISIX和Spring Cloud Gateway等主流解决方案,为不同…

Python 爬虫实战:手把手教你抓取网页数据

在当今数字化时代,网络爬虫已成为数据采集的重要工具。通过爬虫,我们可以从互联网上获取大量有价值的信息,用于数据分析、研究或其他目的。今天,就让我们通过一个简单的实战案例,手把手教你如何使用 Python 抓取网…

(第五次)随机森林和xGboost

(第五次)随机森林和xGboost在大数据分析与计算的算法阵营里,随机森林和 XGBoost 堪称 “顶流”,广泛应用于分类、回归等场景。今天,我们来拆解这两大算法。 一、随机森林 随机森林,简单说就是多棵决策树 “随机组…

华为hcip总纲

这个主要就是记录hcip的分支笔记+视频密码 huawei@2123ip地址规划 都是192.168.50.20网段的华为镜像站https://mirrors.huaweicloud.com/mirrorDetail/5ebe3408c8ac54047fe607f0?mirrorName=openeuler&catalog=os…

haiku

saku宣告这个星期啥也没干。嗯。 和那样的自己有些差距。 宣告一个新的开始。 我情不自禁开始期待。 嗯。可以的。

Asp.Net Core 解决使用 Docker调试时出现“准备容器时发生了一个非关键性错误。项目将继续正常工作。错误为: 路径中具有非法字符。”

参考豆包 https://learn.microsoft.com/en-us/visualstudio/containers/container-launch-settings?view=vs-2022环境软件/系统 版本 说明Windows windows 10 专业版 22H2 64 位操作系统, 基于 x64 的处理器Microsoft…

[Linux] NeoVim安装和Lazyvim配置

[Linux] NeoVim安装和Lazyvim配置$(".postTitle2").removeClass("postTitle2").addClass("singleposttitle");目录环境参考01 下载NeoVim1.1 x-cmd下载1.2 使用homebrew下载1.3 如何使用…

大数据分析基础及应用案例:第三周学习报告 ——Matplotlib 学习报告

一、Pandas 进阶操作:数据合并与分组统计 Pandas 作为 Python 数据分析的核心库,其进阶操作能极大提升数据处理效率。本周重点攻克了数据合并(merge) 与分组统计(groupby) 两大高频操作,并结合商品销售数据完成…

2025.10 训练日志

20251018 模拟赛 身败名裂[A. 最⻓不下降⼦序列] 简单 DP。显然只用考虑形如 1 2 1 2 的四段。 [B. 美⻝节] Hall 定理+线段树维护。 [C. 字符串] 二分哈希+Trie [D. 概率] 简单数数。100 + 100 + 0 + 50 = 250。 T3 是…

全球AI推理扩展技术解析

本文深入解析全球跨区域推理技术架构,详细介绍了智能请求路由机制、IAM权限配置、监控日志系统以及数据安全合规要求,帮助实现AI推理工作负载的全球扩展。全球跨区域AI推理扩展技术解析 随着组织越来越多地将生成式A…

矩阵的秩和逆

秩 定义 矩阵的秩用以描述各列向量或行向量当中线性无关的向量数 求法 通过高斯消元法利用矩阵的线性变换,将每一列或行尽可能多的制造出零的前导 当出现剩余部分全为零或者没有零行出现时,非零行数或列数即为矩阵的…

乱七八糟的知识点

乱七八糟的知识点char a = 0xAB; char 8位 16进制 4位掩码

自监督学习在医疗AI中的技术搭建路径分析(下)

自监督学习在医疗AI中的技术搭建路径分析(下)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &…

AtCoder Beginner Contest 428

A - Grandmas Footsteps 题意:总共\(x\)秒,每\(a\)秒每秒跑\(s\)米,然后停止\(b\)秒。如此循环求总共跑多少秒。 模拟即可。点击查看代码 #include <bits/stdc++.h>using i64 = long long;void solve() {int …