[DarkBZOJ2957]楼房重建

题目

传送门 to DarkBZOJ

思路

问题翻译一下:求前缀 max ⁡ \max max 的变化次数。

首先看看我们怎么减小计算的次数:比如原本是单调递增,然后把中间某个值突然调大,然后又调小。显然调小的时候,涉及一个问题是,想要立刻把后面原有的序列接上去。怎么维护呢?

我们会发现,可能接上去的序列其实是:从此处将序列割裂后,后半部分的所有前缀 max ⁡ \max max 的位置。然而所有可能的割裂点都求出来,这是不现实的。那么我们考虑一下,如果割裂点不是被更改的元素,又怎么样呢?

如果被更改的元素在右半部分,那么左半部分不需要做任何调整,我们可以试图对右半部分 递归,重新得到前缀 max ⁡ \max max 的位置,然后求答案;如果被更改的元素在左半部分,那么右半部分得到的前缀 max ⁡ \max max 的取值点不会变,我们可以试图对左半部分 递归计算,然后右半部分同样直接求出可以接上去的序列。

可是我们很悲催地发现,前缀 max ⁡ \max max 的更改是非常多的……怎么办?有一个非常大胆的想法:当我们知道合并规则的时候,未必要显式合并,只需要 在提取信息时模拟合并过程。比如我们要求出右半部分可以接上去多少,相当于问右端点的前缀 max ⁡ \max max 的一段后缀(大于左半部分最大值);而我们又知道,右半部分的前缀 max ⁡ \max max 是它的两个部分拼接起来得到的;所以当这个前缀只涉及右侧时,往右半部分递归;否则完全包含右侧,直接加上右侧的点数,然后往左半部分递归。

形式化地:建线段树,每个点维护右儿子的前缀 max ⁡ \max max 数量 c u c_u cu 。定义函数 f ( x ) f(x) f(x) 为前缀 max ⁡ \max max 中超过 x x x 的值的数量,记 v l v_l vl 为左儿子的区间最大值,那么 v l ⩽ x v_l\leqslant x vlx 时, f ( x ) = f r s o n ( x ) f(x)=f_{rson}(x) f(x)=frson(x) 。当 v l > x v_l>x vl>x 时, f ( x ) = f l s o n ( x ) + c u f(x)=f_{lson}(x)+c_u f(x)=flson(x)+cu 。所以计算 f ( x ) f(x) f(x) 的复杂度是 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 的。

c u c_u cu 也要用 f ( x ) f(x) f(x) 求解,共 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 个值,所以复杂度 O ( n log ⁡ 2 n ) \mathcal O(n\log^2 n) O(nlog2n)

那么这道题算不算一定要先想到线段树,再想到解法的题呢?感觉也不是吧。只是 motivation \text{motivation} motivation 不太强烈啊……

代码

#include <cstdio> // XJX yyds!!!
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
    
    
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(int x){
    
    
	if(x > 9) writeint(x/10);
	putchar(char((x%10)^48));
}

const int MAXN = 100005;
int n;
namespace SgTree{
    
    
	int val[MAXN<<2]; double mx[MAXN<<2];
	# define _ROOT_ int o=1,int l=1,int r=n
	# define LSON o<<1,l,((l+r)>>1)
	# define RSON o<<1|1,((l+r)>>1)+1,r
	int _calc(const double &qv,_ROOT_){
    
    
		if(l == r) return bool(mx[o] > qv);
		if(mx[o<<1] <= qv) return _calc(qv,RSON);
		return _calc(qv,LSON)+val[o];
	}
	void modify(int qid,double qv,_ROOT_){
    
    
		if(l == r) return void(mx[o] = qv);
		if((qid<<1) <= l+r) modify(qid,qv,LSON);
		else modify(qid,qv,RSON);
		mx[o] = max(mx[o<<1],mx[o<<1|1]);
		val[o] = _calc(mx[o<<1],RSON);
	}
	inline int query(_ROOT_){
    
    
		return _calc(0,LSON)+val[o];
	}
}

int main(){
    
    
	n = readint();
	int m = readint();
	for(int x,y; m; --m){
    
    
		x = readint(), y = readint();
		SgTree::modify(x,double(y)/x);
		writeint(SgTree::query()), putchar('\n');
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42101694/article/details/121345625