Note -「序列元素在线段树上的深度」 感悟
0x01 前言
想法源于一道你谷的毒瘤题目。
这个方面的知识点好像挺新颖的。
于是和 JC 一起想出了该命题的 \(O(n)\) 解法。
0x02 算法本身
总所周知,线段树上的节点都对应表示的原序列里的一些结点。
而我们现在需要解决的问题就是:在极快的时间复杂度内求到每个原序列里的元素对应的元区间在线段树中的深度。
也就是求每个叶子节点的深度。
用线段树建树的朴素做法显然是:\(O(nlogn)\)。
但有些题目会比较恶心,于是我们考虑一种新的做法。
首先明确线段树的一个性质,如果树上有两个节点,且这两个结点表示区间长度相同,则处于相对位置相同的两个分别在这两个节点表示的区间中的原序列中的元素表示的元区间分别到这两个节点的距离相等。(好抽象 www。
于是我们将其剥离出来。
即有两个结点 \(p,q\),其中 \(p\) 表示区间 \(A\),\(q\) 表示区间 \(B\),且区间 \(A\) 的右端点为 \(A_l\),区间 \(B\) 的右端点为 \(B_l\),且记 \(f(x)\) 表示 \(x\) 为当前所在区间的第几个元素。
则对于任意两点 \(m,n\) ,\(m \in A,n \in B, f(m) = f(n)\),一定有 \(p\) 到表示 \(m\) 的元区间的叶子节点的距离等于一定有 \(q\) 到表示 \(n\) 的元区间的叶子节点的距离。
这其实很显然吧。。因为对于每个表示区间长度的节点,我们线段树往下划分的方式是不变的。
接下来,我们记 \(dep(x)\) 表示 \(x\) 这个元区间到根节点的距离,即表示 \(x\) 这个元区间的叶子节点的深度。\(Dep(x)\) 表示 \(x\) 这个节点的深度。
那么如果我们现在遍历到了一个节点 \(Q\),它表示的区间长度为 \(len\),而我们之前也遍历过一个表示区间长度为 \(len\) 的节点 \(P\),则定会有 \(dep(x) = dep(y) - Dep(P) + Dep(Q) (x \in Q,y \in P, f(x) = f(y))\)。
这是因为我们有刚刚那个性质嘛,\(x\) 这个元区间对应的叶子节点的深度可以分解为这个节点到 \(Q\) 的距离和 \(Q\) 的深度。因为 \(y\) 的深度也可以同样分解,所以前者就等于 \(dep(y) - Dep(p)\)。
那么我们可以利用一个 dfs,遍历线段树上的节点,如果遇到一个节点且之前遇到过表示区间长度相同的节点,则我们可以直接用之前那个点对当前节点表示区间内的所有元素进行深度转移,然后这个分支就可以结束了。
因为有记忆化,且你会发现每个节点我们只会更新一次,于是这就是个类 \(O(n)\) 算法。
0x03 部分实现
// q 是一个结构体。
// flag 表示之前是否访问过。
// l 表示上一个访问过的表示区间的长度和当前的一样的节点表示的区间的左端点。
// x 表示上一个访问过的表示区间的长度和当前的一样的节点的深度。
// 按照刚刚推的式子模拟即可。
void Get_Dep(int l, int r, int cnt) {
if(q[r - l + 1].flag) {
for(int i = l; i <= r; i++)
dep[i] = dep[i - l + q[r - l + 1].l] - q[r - l + 1].x + cnt;
return ;
}
if(l == r) {
dep[l] = cnt;
return ;
}
int mid = (l + r) >> 1;
Get_Dep(l, mid, cnt + 1);
Get_Dep(mid + 1, r, cnt + 1);
q[r - l + 1].flag = true;
q[r - l + 1].l = l;
q[r - l + 1].x = cnt;
}
0x04 应用场景
这道题就可以用我们的思路进行预处理。
首先此题是求在线段树中从根到某一叶子节点经过路径权值和的期望。
朴素期望公式:一颗维护区间和的线段树,答案为每个节点表示的权值乘上每个节点的深度,然后在将它们全部加起来。
于是我们将每个节点的权值再返回到原序列中。
设原序列中元素 \(x\) 表示的元区间的深度为 \(g(x)\),其表示的数为 \(v(x)\)。
则原序列的每个元素会对答案产生的贡献为:\(v(x) \times \sum_{i = 1}^{g(x)} 2^i\)。
很显然当前这个元素在线段树种,会在其元区间到根的每一个节点表示的区间里出现。
其中 \(\sum_{i = 1}^{g(x)} 2^i\) 显然可以用上述算法预处理出来。
那么考虑区修。设所改区间为 \([l, r]\)。增加量为 \(x\)。
则这次修改对答案产生的贡献就是 \(Δ \times \sum_{x = l}^r\sum_{i = 1}^{g(x)} 2^i\)。
那么再维护一个 \(\sum_{i = 1}^{g(x)} 2^i\) 的前缀和不就结了吗?
(注,此题若用 double 会错掉一个点,可能与数据精度有关,建议直接使用 long long。
#include <cstdio>
typedef long long LL;
int read() {
int k = 1, x = 0;
char s = getchar();
while (s < '0' || s > '9') {
if (s == '-')
k = -1;
s = getchar();
}
while (s >= '0' && s <= '9') {
x = (x << 3) + (x << 1) + s - '0';
s = getchar();
}
return x * k;
}
const int MAXN = 1e6 + 5;
int a[MAXN], dep[MAXN];
LL w[MAXN], sum[MAXN];
struct node {
bool flag;
int l, x;
node() {}
node(bool Flag, int L, int X) {
flag = Flag;
l = L;
x = X;
}
} q[MAXN];
void Get_Dep(int l, int r, int cnt) {
if(q[r - l + 1].flag) {
for(int i = l; i <= r; i++)
dep[i] = dep[i - l + q[r - l + 1].l] - q[r - l + 1].x + cnt;
return ;
}
if(l == r) {
dep[l] = cnt;
return ;
}
int mid = (l + r) >> 1;
Get_Dep(l, mid, cnt + 1);
Get_Dep(mid + 1, r, cnt + 1);
q[r - l + 1].flag = true;
q[r - l + 1].l = l;
q[r - l + 1].x = cnt;
}
int main() {
int n = read(), m = read(), qwq = read();
Get_Dep(1, n, 1);
w[1] = qwq;
for(int i = 2; i <= 23; i++)
w[i] = w[i - 1] + (qwq >> (i - 1));
for(int i = 1; i <= n; i++)
sum[i] = sum[i - 1] + w[dep[i]];
LL ans = 0;
for(int i = 1; i <= n; i++) {
a[i] = read();
ans += (a[i] * (sum[i] - sum[i - 1]));
}
for(int i = 1; i <= m; i++) {
int l = read(), r = read(), x = read();
ans += ((sum[r] - sum[l - 1]) * x);
printf("%lld\n", ans);
}
return 0;
}
Note -「序列元素在线段树上的深度」 感悟的更多相关文章
- LOJ 3059 「HNOI2019」序列——贪心与前后缀的思路+线段树上二分
题目:https://loj.ac/problem/3059 一段 A 选一个 B 的话, B 是这段 A 的平均值.因为 \( \sum (A_i-B)^2 = \sum A_i^2 - 2*B \ ...
- CF 1405E Fixed Point Removal【线段树上二分】
CF 1405E Fixed Point Removal[线段树上二分] 题意: 给定长度为\(n\)的序列\(A\),每次操作可以把\(A_i = i\)(即值等于其下标)的数删掉,然后剩下的数组 ...
- HDU 1087 Super Jumping! Jumping! Jumping!(求LSI序列元素的和,改一下LIS转移方程)
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1087 Super Jumping! Jumping! Jumping! Time Limit: 20 ...
- LibreOJ #6190. 序列查询(线段树+剪枝)
莫队貌似是过不了的,这题是我没见过的科技... 首先区间按右端点排序,然后一个扫描线,扫到某个区间右端点时候计算答案,线段树上节点的信息并不需要明确定义,我们只要求线段树做到当前扫到now时,查询[L ...
- hdu 5930 GCD 线段树上二分/ 强行合并维护信息
from NOIP2016模拟题28 题目大意 n个点的序列,权值\(<=10^6\) q个操作 1.单点修改 2.求所有区间gcd中,不同数个数 分析 1.以一个点为端点,向左或向右的gcd种 ...
- HDU 4747 Mex【线段树上二分+扫描线】
[题意概述] 一个区间的Mex为这个区间没有出现过的最小自然数,现在给你一个序列,要求求出所有区间的Mex的和. [题解] 扫描线+线段树. 我们在线段树上维护从当前左端点开始的前缀Mex,显然从左到 ...
- [bzoj2962]序列操作_线段树_区间卷积
序列操作 bzoj-2962 题目大意:给定一个n个数的正整数序列,m次操作.支持:1.区间加:2.区间取相反数:3.区间求选c个数的乘积和. 注释:$1\le n,m\le 5\cdot 10^4$ ...
- [NOIP2015模拟10.27] [JZOJ4270] 魔道研究 解题报告(动态开点+权值线段树上二分)
Description “我希望能使用更多的魔法.不对,是预定能使用啦.最终我要被大家称呼为大魔法使.为此我决定不惜一切努力.”——<The Grimoire of Marisa>雾雨魔理 ...
- 【洛谷5537】【XR-3】系统设计(哈希_线段树上二分)
我好像国赛以后就再也没有写过 OI 相关的博客 qwq Upd: 这篇博客是 NOIP (现在叫 CSP 了)之前写的,但是咕到 CSP 以后快一个月才发表 -- 我最近这么咕怎么办啊 -- 题目 洛 ...
随机推荐
- 借助ADB冻结与卸载Android系统应用(免ROOT)
背景: 我妈的手机饱受系统应用广告推送之苦,每天都能在通知栏里收到好几条广告.为了给她个清净,本篇博文应运而生. 目标: 卸载安卓系统应用 所用工具: 硬件:我妈的手机(魅蓝5) PC端:Minima ...
- 了解mybatis
什么是mybatis? MyBatis 是一款优秀的持久层框架,它支持自定义 SQL.存储过程以及高级映射.MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作.MyBati ...
- IIS方式部署项目发布上线
VS2019如何把项目部署和发布 这里演示:通过IIS文件publish的方式部署到Windows本地服务器上 第一步(安装IIS) 1.在自己电脑上搜索Windows功能里的[启用或关闭Window ...
- 异步加载数据——turn.js
var tostore = GetQueryString("tostore"); var photo_id = GetQueryString("photo_id" ...
- python读写excel表格,4泼水板房
import shutilimport osfrom openpyxl import load_workbookfrom xlutils.copy import copyimport win32com ...
- a commponent required a bean of type XXXXXX that could not be found-2022新项目
一.问题由来 目前刚入职一家新公司不久,公司的新项目采用DDD驱动领域设计来进行开发,架构这一块使用的是阿里巴巴开源的最新框架COLA4.0的架构. 主要是这个框架里面的分层设计.主要分为四层:ada ...
- c++ :STL
基础知识 容器 容器就是一些模板类的集合,不同之处就是容器中封装的是数据结构 1.序列容器 主要有vector向量容器.list列表容器.deque双端队列容器 元素在容器中是无序的 2.排序容器 包 ...
- JavaScript 任务池
JavaScript 任务池 本文写于 2022 年 5 月 13 日 线程池 在多线程语言中,我们通常不会随意的在需要启动线程的时候去启动,而是会选择创建一个线程池. 所谓线程池,本意其实就是(不止 ...
- Hadoop安装学习(第三天)
学习任务: 1.解压jdk和hadoop包 2.安装jdk 3.修改hadoop配置文件 4.hadoop格式化 5.hadoop启动 出现的问题:hadoop可以正常启动,但是端口9000丢失,导致 ...
- uniapp设置竖屏
//在APP.vue中的onLaunch钩子写入plus.screen.lockOrientation('portrait-primary');