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 以后快一个月才发表 -- 我最近这么咕怎么办啊 -- 题目 洛 ...
随机推荐
- VMware安装Ubuntu20(图文教程,超详细)
VMware安装Ubuntu20(图文教程,超详细) 此文讲述使用 VMware 工具安装 Ubuntu 系列虚拟机,不同位数和不同版本的 Ubuntu 安装过程相差无几,这里以 Ubuntu20 6 ...
- C++基础-1-内存管理(全局区、堆区、栈区)
1. 内存管理 1.1 全局区 1 #include<iostream> 2 using namespace std; 3 4 // 全局变量 5 int g_a = 10; 6 int ...
- 一文彻底搞懂MySQL分区
一个执着于技术的公众号 一.InnoDB逻辑存储结构 首先要先介绍一下InnoDB逻辑存储结构和区的概念,它的所有数据都被逻辑地存放在表空间,表空间又由段,区,页组成. 段 段就是上图的segment ...
- Linux系统创建可执行文件软链接
技术背景 由于创建软链接这个事情,在算法开发的日常中使用到的并不是很多,因此本文也是做一个简单的回顾.这里我们使用的案例是通过TMalign这个蛋白质打分文件,在编译好可执行文件之后,可以使用建立软链 ...
- Spring 源码(8)Spring BeanPostProcessor的注册、国际化及事件发布机制
上一篇文章https://www.cnblogs.com/redwinter/p/16198942.html介绍了Spring的注解的解析过程以及Spring Boot自动装配的原理,大概回顾下:Sp ...
- 哈工大软件构造Lab1(2022)
目录 一.实验目标概述 二.实验环境配置 1.安装编写java程序的IDE--IntelliJ IDEA 2.安装Git 3.安装Junit 4.GitHub Lab1仓库的URL地址 三.实验过程 ...
- python操作MySQL、事务、SQL注入问题
python操作MySQL python中支持操作MySQl的模块很多 其中最常见就是'pymysql' # 属于第三方模块 pip3 install pymysql # 基本使用 import py ...
- 如何使用Python实现图像融合及加法运算?
摘要:本篇文章主要讲解Python调用OpenCV实现图像融合及加法运算,包括三部分知识:图像融合.图像加法运算.图像类型转换. 本文分享自华为云社区<[Python图像处理] 五.图像融合.加 ...
- 项目中导入本地jar包问题
1. 问题 一个Maven项目,需要依赖一个本地jar包,以如下方式引用: <dependency> <groupId>xxx.sdk</groupId> < ...
- 01C语言基础(二)
Day07 笔记 指针和函数: 栈 帧: 当函数调用时,系统会在 stack 空间上申请一块内存区域,用来供函数调用,主要存放 形参 和 局部变量(定义在函数内部). 当函数调用结束,这块内存区域自动 ...