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;
}

浙公网安备 33010602011771号