P4198 楼房重建题解(线段树, 分治)

题目描述

题面

简要题意:

给你一个长度为 n n n 的序列 a i a_i ai ( n ≤ 1 0 5 n \leq 10^5 n≤105),要求进行 m m m 次操作 ( m ≤ 1 0 5 m \leq 10^5 m≤105) 。操作分两种:

1.单点修改。

2.查询整个序列中有多少个位置 x x x 满足 a x a_x ax 大于其前缀。 即 ∀ j < x , a j < a x \forall j < x,a_j < a_x ∀j<x,aj<ax 。输出这样的 x x x 的数量。

分析

设计 单点修改区间查询 考虑使用 线段树 维护信息。我们在线段树节点中维护 s u m p sum_p sump ,表示 p p p 这个节点对应区间 [ l p , r p ] [l_p, r_p] [lp,rp] 中,满足 对应值大于 以 l p l_p lp 为开头的前缀 的位置数量。

那么对于 查询 ,如果 1 1 1 号节点对应整个区间,答案就是 s u m 1 sum_1 sum1。

对于 修改 ,递归到叶子进行的修改是简单的。我们考虑如何将儿子的信息传递给父亲。设 u u u 为一个父亲节点。 l s u ls_u lsu 和 r s u rs_u rsu 分别为其左右儿子,并且内部的信息已经处理正确。那么首先 左儿子内部 满足 大于区间左端点为开头的前缀 的 位置数量 可以直接传递,因此有 s u m l s u → s u m u sum_{ls_u} \to sum_u sumlsu→sumu。对于右儿子,它的 s u m sum sum 不能直接传递,因为其区间内的每一个数还要与 左儿子区间的最大值比较 。因此我们需要在线段树内再维护一个 M a x n Maxn Maxn 表示区间最大值。然后我们设 c a l c ( p , v a l ) calc(p, val) calc(p,val) 表示 p p p节点对应 区间内部 同时满足大于区间前缀和 v a l val val 的位置数量。那么 r s u rs_u rsu 对于 u u u 的贡献就是 c a l c ( r s u , M a x n l s u ) calc(rs_u, Maxn_{ls_u}) calc(rsu,Maxnlsu)。

我们考虑 c a l c ( n o d e , v a l ) calc(node, val) calc(node,val) 如何求解。发现如果 M a x n l s n o d e > v a l Maxn_{ls_{node}} > val Maxnlsnode>val,那么 r s n o d e rs_{node} rsnode 在 n o d e node node 对应区间的贡献就可以全部算进来。也就是说,如果 待求解区间的左半区间最大值大于 v a l val val,那么右半区间对 待求解区间的贡献 就等于其对 待求解区间的 s u m sum sum 的贡献 。我们可以直接返回 c a l c ( l s n o d e , v a l ) + s u m n o d e − s u m l s n o d e calc(ls_{node}, val) + sum_{node} - sum_{ls_{node}} calc(lsnode,val)+sumnode−sumlsnode。

如果 M a x n l s n o d e ≤ v a l Maxn_{ls_{node}} \leq val Maxnlsnode≤val,那可以发现左半区间是不会有贡献的,我们直接返回 c a l c ( r s n o d e , v a l ) calc(rs_{node}, val) calc(rsnode,val) 就好。

这样发现 c a l c calc calc 函数所求区间每次长度减半,最多执行 l o g 2 n log_2{n} log2n 次。而线段树中每次修改最多 pushup l o g 2 n log_2n log2n 次,因此复杂度是 O ( m l o g 2 2 n ) O(mlog_2^2n) O(mlog22n)。

CODE:

cpp 复制代码
// 问题模型化: 一个长度为n的序列,支持单点修改和在线查询整个序列有多少个位置上的值严格大于其前缀
// sol: 线段树 + 分治 时间复杂度 O(nlog^2n)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef double db;
struct SegmentTree {
	int l, r, sum; db Maxn; // sum 表示这个节点所在的区间(前缀的起始位置为区间左端点)有多少符合条件的点
	#define l(x) t[x].l
	#define r(x) t[x].r
	#define sum(x) t[x].sum
	#define Maxn(x) t[x].Maxn
}t[N * 4];
int n, m;
db EPS = 1e-20;
int calc(int p, db val) { // calc(p, val) 表示查询 p节点对应区间 大于val 并且大于其前缀的节点数
	if(l(p) == r(p)) {return (Maxn(p) > val);}
	if(Maxn(p << 1) > val) return (sum(p) - sum(p << 1)) + calc(p << 1, val);
	else return calc(p << 1 | 1, val);
}
void update(int p) {
	Maxn(p) = max(Maxn(p << 1), Maxn(p << 1 | 1));
	sum(p) = sum(p << 1) + calc(p << 1 | 1, Maxn(p << 1));
}
void build(int p, int l, int r) {
	l(p) = l, r(p) = r;
	if(l == r) {
		sum(p) = 0;
		Maxn(p) = 0;
		return ;
	}
	int mid = (l + r >> 1);
	build(p << 1, l, mid);
	build(p << 1 | 1, mid + 1, r);
	update(p);
}
void change(int p, int pos, db c) {
	if(l(p) == r(p)) {
		Maxn(p) = c;
		if(Maxn(p) - 0.0 < EPS) sum(p) = 0;
		else sum(p) = 1;
		return ;
	}
	int mid = (l(p) + r(p) >> 1);
	if(pos <= mid) change(p << 1, pos, c);
	else change(p << 1 | 1, pos, c);
	update(p);
}
int main() {
	scanf("%d%d", &n, &m);
	build(1, 1, n);
	while(m -- ) {
		int x, y; scanf("%d%d", &x, &y);
		change(1, x, (db)(1.0 * y / x));
		printf("%d\n", sum(1));
	}
	return 0;
}
相关推荐
Voyager_431 分钟前
图像处理踩坑:浮点数误差导致的缩放尺寸异常与解决办法
数据结构·图像处理·人工智能·python·算法
文艺倾年36 分钟前
【八股消消乐】手撕分布式协议和算法(基础篇)
分布式·算法
万岳科技系统开发1 小时前
从源码优化外卖配送系统:算法调度、智能推荐与数据分析应用
算法·数据挖掘·数据分析
信奥卷王4 小时前
[GESP202503 五级] 原根判断
java·数据结构·算法
兮山与4 小时前
算法4.0
算法
nju_spy4 小时前
力扣每日一题(二)任务安排问题 + 区间变换问题 + 排列组合数学推式子
算法·leetcode·二分查找·贪心·排列组合·容斥原理·最大堆
初听于你4 小时前
高频面试题解析:算法到数据库全攻略
数据库·算法
翟天保Steven4 小时前
ITK-基于Mattes互信息的二维多模态配准算法
算法
代码对我眨眼睛4 小时前
226. 翻转二叉树 LeetCode 热题 HOT 100
算法·leetcode·职场和发展
黑色的山岗在沉睡5 小时前
LeetCode 494. 目标和
算法·leetcode·职场和发展