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 以后快一个月才发表 -- 我最近这么咕怎么办啊 -- 题目 洛 ...
随机推荐
- 关键字 global和nonlocal
globale 表示从全局把一个变量(比如a)引入局部,后面的变量全是此变量a 使用 globale 变量名 # 全局变量一般是不能随意的修改的 # a = 10 # def func(): # ...
- 在博客文章中使用mermaid 定义流程图,序列图,甘特图
概述 Mermaid(美人鱼)是一套markdown语法规范,用来在markdown文档中定义图形,包括流程图.序列图.甘特图等等. 它的官方网站是 https://mermaid-js.github ...
- Node.js + TypeScript + ESM +HotReload ( TypeScript 类型的 Node.js 项目从 CommJS 转为 ESM 的步骤)
当前 Node.js 版本:v16.14.0 当前 TypeScript 版本:^4.6.3 步骤 安装必要的依赖 yarn add -D typescript ts-node @tsconfig/n ...
- 2. springboot加载配置参数顺序
加载顺序依次是:1.jar的classes里面的application.properties 2.当前路径下config里面的application.properties 3.jar的classes里 ...
- 广义SAM
参考 还有首先你要会SAM吧~ 用途 相比与单串SAM,广义自动机能存储的是多个字符串. 有两种写法,第一种是离线利用trie树结构,第二种是在线伪广义SAM 离线+Trie 首先构建出trie树. ...
- go-zero 微服务实战系列(二、服务拆分)
微服务概述 微服务架构是一种架构风格,它将一个大的系统构建为多个微服务的集合,这些微服务是围绕业务功能构建的,服务关注单一的业务功能,这些服务具有以下特点: 高度可维护和可测试 松散的耦合 可独立部署 ...
- @vue/cli3+配置build命令构建测试包&正式包
上一篇博客介绍了vue-cli2.x配置build命令构建测试包和正式包,但现在前端开发vue项目大多数使用新版@vue/cli脚手架搭建vue项目(vue create project-name) ...
- 【Java面试】请你简单说一下Mysql的事务隔离级别
一个工作了6年的粉丝,去阿里面试,在第一面的时候被问到"Mysql的事务隔离级别". 他竟然没有回答上来,一直在私信向我诉苦. 我说,你只能怪年轻时候的你,那个时候不够努力导致现在 ...
- Nginx安装及支持https代理配置和禁用TSLv1.0、TSLv1.1配置
Linux安装Nginx Nginx安装及支持https代理配置和禁用TSLv1.0.TSLv1.1配置. 下载安装包 [root@localhost ~]# wget http://nginx.or ...
- VTK 在WINDOWS上的安装使用
参考:http://www.vtk.org/Wiki/VTK/Building/Windows#Step_5_-_Open_the_Visual_Studio_project