*题解:P5324 [BJOI2019] 删数

原题链接

解析

考虑初始数列的答案怎么求。显然,数字顺序不影响答案,故开一个桶 \(b\) 把所有数字的出现次数存进来。根据题意,一个序列可以删空当且仅当如果令每种数 \(b_i\) 覆盖其左边的区间 \([i - b_i + 1,i]\),那么最终每个位置都能被覆盖到。对于没有被覆盖到的位置,必定可以通过一次操作修改被重复覆盖以及超出边界的点,从而将其覆盖。而且,通过一次操作至多能更改两个位置的状态,且其中一个位置必定未被覆盖。故这种操作方式是最优的,答案为没有覆盖到的位置数量。

对于只有单点修改的情况,直接维护一个覆盖的数组 \(c\) 表示每个位置被覆盖的次数即可。修改必定是某个位置减一某个位置加一,维护数组中 \(0\) 的个数是容易的。

现在有了数列整体减,对于 \(c\) 而言,相当于里面的元素整体向左平移一个单位,这个东西不好维护,所以不妨换个角度考虑,把查询区间向右移动一个单位即可。设当前查询区间为 \([L,R]\),那么只需要移出 \(L\),加入 \(R + 1\) 即可,同样可以直接维护。

对于数列整体加,类似地,把查询区间向左移动一个单位。然而,对于查询区间外的数,可能会对区间内做多余的贡献,设当前查询区间为 \([L,R]\),每左移一个单位,就要减去 \(b_R\) 所做的覆盖。这一步骤需要区间减。同时这也使得数列整体加操作要加上 \(b_{R + 1}\) 所作的覆盖。

对于单点修改操作,修改的值需要根据当前询问区间加上一个偏移量 \(x\),怎么判断是加还是减呢?为了方便查询我们将询问区间视为可变的,但实际上它是不动的,我们想要添加一个值也是在这个不动的区间里添加,所以添加的元素相对于区间的位置是固定的。例如当整体加一时,区间向左移动,后面添加的元素也视为向左移动,故 \(x \leftarrow x-1\)

因此,我们需要维护数列区间加,区间 \(0\) 的个数。对于区间 \(0\) 的个数,由于 \(c\) 中元素非负,所以我们只关心区间最小值是否为 \(0\),如果是,其个数为多少。线段树维护即可。

时间复杂度 \(O(m \log n)\)

代码

注意由于整体加减的存在,需要开三倍空间。

/*
*/
#include <bits/stdc++.h>
#define ls(x) ((x) << 1)
#define rs(x) ((x) << 1 | 1)
#define mid (l + r >> 1)
using namespace std;
const int N = 150000 + 5,M = 3 * N;
typedef long long ll;
typedef pair<int,int> pii;
int a[N],b[M];
int mn[M << 2],cnt[M << 2],tag[M << 2];
void push_up(int p){
	mn[p] = min(mn[ls(p)],mn[rs(p)]);
	cnt[p] = 0; 
	if(mn[ls(p)] <= mn[rs(p)]) cnt[p] += cnt[ls(p)];
	if(mn[ls(p)] >= mn[rs(p)]) cnt[p] += cnt[rs(p)];
}
void build(int p,int l,int r){
	if(l == r){
		cnt[p] = 1;
		return;
	}
	build(ls(p),l,mid),build(rs(p),mid + 1,r);
	push_up(p);
}
void add_tag(int p,int k){
	tag[p] += k;
	mn[p] += k;
}
void push_down(int p){
	if(!tag[p]) return;
	add_tag(ls(p),tag[p]);
	add_tag(rs(p),tag[p]);
	tag[p] = 0;
}
void modi(int p,int l,int r,int k,int x){
	if(l == r){
		mn[p] = x;
		return;
	}
	push_down(p);
	modi(ls(p),l,mid,k,x),modi(rs(p),mid + 1,r,k,x);
	push_up(p);
}
int ask(int p,int l,int r,int L,int R){
	if(l > R || r < L) return 0 ;
	if(l >= L && r <= R){
		return (mn[p] == 0) * cnt[p];
	}
	push_down(p);
	return ask(ls(p),l,mid,L,R) + ask(rs(p),mid + 1,r,L,R);
}
void add(int p,int l,int r,int L,int R,int x){
	if(l > R || r < L) return;
	if(l >= L && r <= R){
		add_tag(p,x);
		return;
	} 
	push_down(p);
	add(ls(p),l,mid,L,R,x),add(rs(p),mid + 1,r,L,R,x);
	push_up(p);
}
int val(int p,int l,int r,int k){
	if(l > k || r < k) return 2e9;
	if(l == r){
		return mn[p];
	}
	push_down(p);
	return min(val(ls(p),l,mid,k),val(rs(p),mid + 1,r,k));
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i] += N - 1;
		b[a[i]]++;
	}
	int l = N,r = l + n - 1;
	build(1,1,M);
	for(int i=l;i<=r;i++){
		add(1,1,M,i - b[i] + 1,i,1);
	}
	int c = 0;
	while(m--){
		
		int p,x;
		cin>>p>>x;
		if(p == 0){
			if(x == 1){
				add(1,1,M,r - b[r] + 1,r,-1);
				c--;
				l--,r--;
			}else{
				c++;
				l++,r++;
				add(1,1,M,r - b[r] + 1,r,1);
				
			}
			cout<<ask(1,1,M,l,r)<<'\n';
		}else{
			x += N - 1 + c;
			if(a[p] <= r)
			add(1,1,M,a[p] - b[a[p]] + 1,a[p] - b[a[p]] + 1,-1);
			b[a[p]]--;
			if(x <= r)
			add(1,1,M,x - b[x],x - b[x],1);	
			b[x]++;
			a[p] = x;
			cout<<ask(1,1,M,l,r)<<'\n';
		}
	}
	return 0;
}
posted @ 2026-04-23 19:00  yutar  阅读(16)  评论(0)    收藏  举报