LuoguP4198 楼房重建

原题传送门

题目大意

有一个长度为 \(n\) 的序列,\(m\) 次操作,每次操作更改一个位置的值,询问每次操作后全局严格前缀最大不同值的个数。

题目思路

单点修改全局查询可以考虑线段树。

修改是简单的,问题是 pushup 的时候已知两颗左树与右树的状态,该如何将其合并。

发现当前区间的答案(即严格前缀最大不同值的个数)的基础为左区间的答案,接下来考虑右区间。

发现右区间的答案的最小值需要满足大于左区间的最大值,然后考虑如何求满足条件的右区间答案。

设左区间最大值为 \(x\)

若左区间的最大值小于 \(x\),则左区间一定不会造成贡献。反之,整个区间的答案减去左区间的答案就一定能选,因为这个值为右区间在满足大于左区间的最大值的情况下的答案,一定能选。此时去左区间搜索答案即可。

发现这个过程类似一个递归过程,那么边界即 \(l=r\) 的时候,如果当前值大于 \(x\) 则返回 \(1\),反之返回 \(0\)

每次询问会 pushup \(\log n\) 次,每次 pushup\(O(\log n)\)。总体时间复杂度为 \(O(m \times \log^2 n)\)

代码

pushup 的代码详见 pushup1

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INT_MAX (int)(1e18)
#define mid (l+r>>1)

const int N=1e5+10;

int n,m;
int tr2[N<<2];

double a[N],tr1[N<<2];
//tr1 max     tr2 max_len

inline int read(){
	int t=0,f=1;
	register char c=getchar();
	while(c<'0'||c>'9') f=(c=='-')?(-1):(f),c=getchar();
	while(c>='0'&&c<='9') t=(t<<3)+(t<<1)+(c^48),c=getchar();
	return t*f;
}

int pushup1(int bian,int l,int r,double x){//x min>x
	if(tr1[bian]<=x) return 0;  //剪枝 当最大值仍 <=x 时,必定返回 0 
	if(a[l]>x) return tr2[bian]; //剪枝 当第一个值 >x 时,必定可以全选 
	if(l==r){if(a[l]>x) return 1;return 0;}
	if(tr1[bian<<1]>x) return tr2[bian]-tr2[bian<<1]+pushup1(bian<<1,l,mid,x);
	else return pushup1(bian<<1|1,mid+1,r,x);
}

void pushup(int bian,int l,int r){
	tr1[bian]=max(tr1[bian<<1],tr1[bian<<1|1]);
	tr2[bian]=tr2[bian<<1]+pushup1(bian<<1|1,mid+1,r,tr1[bian<<1]);
}

void update(int bian,int l,int r,int x){
	if(l==r){tr1[bian]=a[x],tr2[bian]=1;return;}
	if(x<=mid) update(bian<<1,l,mid,x);
	else update(bian<<1|1,mid+1,r,x);
	pushup(bian,l,r);
}

signed main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		int x=read(),y=read();
		a[x]=(double)y/(double)x;
		update(1,1,n,x);cout<<tr2[1]<<"\n";
	}
	return 0;
}


posted @ 2025-03-27 10:05  ask_silently  阅读(11)  评论(0)    收藏  举报