loj#3094. 「BJOI2019」删数

Description

loj#3094

Solution

首先肯定是考虑在没有修改的情况下,直接给定序列该如何计算该序列需要修改几个数才能删空:

首先,该序列的顺序对答案是没有任何影响的,影响答案的只有某个数的出现次数,这令我们想到在值域数轴上用桶维护每个数的出现次数。

接下来考虑模拟删边的过程,一开始长度为 \(n\),删去所有数列中等于 \(n\) 的数,这等价于将 \(n\) 对应的桶向左推倒,将所有 \(n\) 依次覆盖在数轴上,对所有数的进行这样的操作,有结论:数轴上 \([1,n]\) 区域内未被覆盖的位置的数量就是答案。

证明:

设未被覆盖的位置的数量为 \(w\),首先答案数量必定会 \(\ge w\) ,这是因为数轴上这 \(w\) 个空缺位置必须要被填补,才能使得后面的数被删完后,可以开始删前面的数。其次,由于空缺了 \(w\) 个位置,那么数轴上一定有 \(w\) 个位置被重复覆盖,一定可以取出这 \(w\) 个数将它们放到空位上,因此答案 \(\le w\),所有答案 \(=w\)。

回到原问题,考虑直接维护数轴上每个数被覆盖的情况,那么询问就等价于询问区间中 \(0\) 的个数,这可以通过维护最小值+最小值的出现次数实现。对于修改,单点修改只会改变一个位置的覆盖情况,直接修改即可;整体 \(+1\) 或 \(-1\) 则等价于是让询问区间在数轴上平移,可以通过将数轴向左右扩展 \(150000\) 格,一起维护来实现。同时此时我们只关心询问区间 \([l+1,l+n]\) 区间内的数的贡献,因此每次单点修改都需要考虑其在询问区间内才进行修改,每次询问区间平移一个,都需要更改左右两个位置的桶的贡献,这等价于是一个区间修改,也是易于维护的。

总复杂度 \(\mathcal O(n\log n)\)。

Code

#include<bits/stdc++.h>
using namespace std;
const int N=6e5+10,lim=600000,len=200000;
int n,m,a[N],ct[N],buc[N];
namespace SGT{
	#define lc (p<<1)
	#define rc (p<<1|1)
	#define mid ((l+r)>>1)
	int tr[N<<2],tag[N<<2],mn[N<<2],ct[N<<2];
	inline void pushdown(int p){
		if(!tag[p]) return ;
		tag[lc]+=tag[p];mn[lc]+=tag[p];
		tag[rc]+=tag[p];mn[rc]+=tag[p];
		tag[p]=0;
	}
	inline void pushup(int p){
		mn[p]=min(mn[lc],mn[rc]);ct[p]=0;
		if(mn[p]==mn[lc]) ct[p]+=ct[lc];
		if(mn[p]==mn[rc]) ct[p]+=ct[rc];
	}
	inline void build(int p=1,int l=1,int r=lim){
		tag[p]=0;
		if(l==r){mn[p]=buc[l];ct[p]=1;return ;}
		build(lc,l,mid);build(rc,mid+1,r);
		pushup(p);
	}
	inline void update(int p,int ql,int qr,int v,int l=1,int r=lim){
		if(ql<=l&&r<=qr){
			tag[p]+=v;mn[p]+=v;
			return ;
		}
		pushdown(p);
		if(ql<=mid) update(lc,ql,qr,v,l,mid);
		if(qr>mid) update(rc,ql,qr,v,mid+1,r);
		pushup(p);
	}
	inline int query(int p,int ql,int qr,int l=1,int r=lim){
		if(ql<=l&&r<=qr)
			return mn[p]==0?ct[p]:0;
		pushdown(p);
		int ans=0;
		if(ql<=mid) ans+=query(lc,ql,qr,l,mid);
		if(qr>mid) ans+=query(rc,ql,qr,mid+1,r);
		return ans; 
	}
}
int del,s;
inline void dele(int x){SGT::update(1,x-ct[x]+1,x,-1);}
inline void add(int x){SGT::update(1,x-ct[x]+1,x,1);}
int main(){
	scanf("%d%d",&n,&m);
	del=200000;s=del;
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),a[i]+=del,ct[a[i]]++;
	for(int i=0;i<=n;++i){
		int x=i+del;
		for(int j=x-ct[x]+1;j<=x;++j) buc[j]++;
	}
	SGT::build();
	for(int i=1,op,x;i<=m;++i){
		scanf("%d%d",&op,&x);
		if(op>=1){
			x+=s;
			if(a[op]!=x){
				if(a[op]<=n+s&&a[op]>s) SGT::update(1,a[op]-ct[a[op]]+1,a[op]-ct[a[op]]+1,-1);
				ct[a[op]]--;
				a[op]=x;
				ct[x]++;
				if(x<=n+s&&x>s) SGT::update(1,x-ct[x]+1,x-ct[x]+1,1);
			}
		}
		else{
			if(x==-1)
				dele(s+1),add(s+n+1),s++;
			else
				dele(s+n),add(s),s--;
		}
		printf("%d\n",SGT::query(1,s+1,s+n));
	}	
	return 0;
}
上一篇:中缀表达式转后缀表达式


下一篇:弹幕