「BJOI 2019」删数

传送门


problem

对于任意一个数列,如果能在有限次进行下列删数操作后将其删为空数列,则称这个数列可以删空。一次删数操作定义如下:

  • 记当前数列长度为 k k k,则删掉数列中所有等于 k k k 的数。

现有一个长度为 n n n 的数列 a a a,有 m m m 次修改操作,第 i i i 次修改后你要回答:经过 i i i 次修改后的数列 a a a,至少还需要修改几个数才可删空?

每次修改操作为单点修改或数列整体加一或数列整体减一。

数据范围: 1 ≤ n , m ≤ 150000 1≤n,m≤150000 1n,m150000


solution

这道题的想法很妙啊。

首先,我们只需考虑每个数 i i i 的出现次数 c n t i cnt_i cnti并不关心数的排列顺序

考虑一个结论,对于 i i i,我们把 [ i − c n t i + 1 , i ] [i-cnt_i+1,i] [icnti+1,i] 这个区间进行一个区间覆盖,答案就是没有被覆盖的数的个数。

怎么理解这个东西呢?这有点像贪心。首先,对于 [ 1 , n ] [1,n] [1,n] 的数是有可能不用修改的,考虑怎么最大化不修改的数的数量。如果对于 i i i,把 [ i − c n t i + 1 , i ] [i-cnt_i+1,i] [icnti+1,i] 覆盖,就相当于这一段可以不改。重复覆盖就相当于 “ “ 冲突 ” ” ,就会空出几个位置。

修改的话,单点修改比较好做,整体加减 1 1 1 就把区间整体移一下就好了。

然后注意一下边界的问题就可以了。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)


code

#include<bits/stdc++.h>
#define N 1000005
using namespace std;
int n,m,S,lim,a[N];
namespace Segment_Tree{
	struct Seg{int add,zero,Min,cnt;}T[N<<2];
	#define mid ((l+r)>>1)
	void pushup(int root){
		T[root].Min=min(T[root<<1].Min,T[root<<1|1].Min);
		T[root].cnt=(T[root].Min==T[root<<1].Min?T[root<<1].cnt:0)+(T[root].Min==T[root<<1|1].Min?T[root<<1|1].cnt:0);
		T[root].zero=T[root<<1].zero+T[root<<1|1].zero;
	}
	void pushnow(int root,int val){
		T[root].Min+=val;
		T[root].add+=val;
		T[root].zero=(T[root].Min==0)?T[root].cnt:0;
	}
	void pushdown(int root){
		if(!T[root].add)  return;
		pushnow(root<<1,T[root].add);
		pushnow(root<<1|1,T[root].add);
		T[root].add=0;
	}
	void build(int root,int l,int r){
		if(l==r){T[root].zero=T[root].cnt=1;return;}
		build(root<<1,l,mid),build(root<<1|1,mid+1,r);
		pushup(root);
	}
	void Modify(int root,int l,int r,int x,int y,int val){
		if(l>=x&&r<=y)  {pushnow(root,val);return;}
		pushdown(root);
		if(x<=mid)  Modify(root<<1,l,mid,x,y,val);
		if(y> mid)  Modify(root<<1|1,mid+1,r,x,y,val);
		pushup(root);
	}
	int Query(int root,int l,int r,int x,int y){
		if(l>=x&&r<=y)  return T[root].zero;
		pushdown(root);
		if(y<=mid)  return Query(root<<1,l,mid,x,y);
		if(x> mid)  return Query(root<<1|1,mid+1,r,x,y);
		return Query(root<<1,l,mid,x,y)+Query(root<<1|1,mid+1,r,x,y);
	}
	#undef mid
}
using namespace Segment_Tree;
int num[N];
void change(int x,int val){
	int k=num[S+x]+(val>0);
	num[S+x]+=val;
	if(x<=n)  Modify(1,1,lim,S+x-k+1,S+x-k+1,val);
}
int main(){
	scanf("%d%d",&n,&m),S=n+m+1,lim=S*2+1,build(1,1,lim);
	for(int i=1;i<=n;++i)  scanf("%d",&a[i]),change(a[i],1);
	int p,x,delta=0;
	while(m--){
		scanf("%d%d",&p,&x);
		if(p>0)  change(a[p]+delta,-1),a[p]=x-delta,change(a[p]+delta,1);
		else  if(x==1){
			int pos=S+n;S--,delta++;
			if(num[pos]>0)  Modify(1,1,lim,pos-num[pos]+1,pos,-1);
		}
		else{
			int pos=S+n+1;S++,delta--;
			if(num[pos]>0)  Modify(1,1,lim,pos-num[pos]+1,pos,1);  
		}
		printf("%d\n",Query(1,1,lim,S+1,S+n));
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值