lgP5324 [BJOI2019]删数加强版

题目给定的操作等价于:

覆盖\([i - cnt_i + 1 , i]\)

最后答案就是 \([S-T] - [S-T]中覆盖了的长度 = ans\)

单点修改很好做,直接修改一下并集就好了

但是区间整体 +/- 1就不是很好办了

直接平移一下区间就好了

注意到 如果一个 区间的右端点在 当前确定的区间右边 那么不会对答案产生贡献

/*
区间加减,区间并 
*/
#include<bits/stdc++.h>
#define MAXN 1000005
typedef long long ll;
using namespace std;

ll n,m;
ll a[MAXN],cnt[MAXN];
ll lim = 450000+5;

struct node{ll mi,ans,cnt,ad;}t[MAXN * 5];

void up(int rt){
	t[rt].mi = min(t[rt << 1].mi , t[rt << 1 | 1].mi);
	t[rt].cnt = ((t[rt << 1].mi == t[rt].mi) ? t[rt << 1].cnt : 0) + ((t[rt << 1 | 1].mi == t[rt].mi) ? t[rt << 1 | 1].cnt : 0);//没被覆盖的区间 
	t[rt].ans = t[rt << 1].ans + t[rt << 1 | 1].ans;
}

void build(int rt , int l , int r){
	if(l == r)return (void)(t[rt].ans = t[rt].cnt = 1);
	int mid = (l + r) >> 1;
	build(rt << 1 , l , mid);
	build(rt << 1 | 1 , mid + 1 , r);
	up(rt);
}

void change(int rt , int c){
	t[rt].mi += c;
	t[rt].ans = (t[rt].mi == 0 ? t[rt].cnt : 0);
	t[rt].ad += c;
}

void push_down(int rt){
	if(!t[rt].ad)return;
	change(rt << 1 , t[rt].ad);
	change(rt << 1 | 1 , t[rt].ad);
	t[rt].ad = 0;
}

void update(int rt , int l , int r , int x , int y , int z){
	if(r < x || l > y)return;
	if(x <= l && r <= y){
		change(rt , z);
		return;
	}
	push_down(rt);
	int mid = (l + r) >> 1;
	update(rt << 1 , l , mid , x , y , z);
	update(rt << 1 | 1 , mid + 1 , r , x , y , z);
	up(rt);
}

int que(int rt , int l , int r , int x , int y){
	if(r < x || l > y)return 0;
	if(x <= l && r <= y)return t[rt].ans;
	push_down(rt);
	int mid = (l + r) >> 1 , zz = 0;
	zz = zz + que(rt << 1 , l , mid , x , y);
	zz = zz + que(rt << 1 | 1 , mid + 1 , r , x , y);
	up(rt);
	return zz;
}

void dec(int x , int v){
	int k = x - cnt[x] + 1 - (v > 0);
	update(1 , 1 , lim , k , k , v);
	cnt[x] += v;
}

int main(){
	scanf("%d%d" , &n , &m);
	int ST = 1 + 150000;
	build(1 , 1 , lim);
	for(int i = 1 ; i <= n ; i++){
		scanf("%lld" , &a[i]) , a[i] += ST;	
		dec(a[i] , 1);
	}
	ll p,x;
	while(m--){
		scanf("%lld%lld" , &p , &x);
		if(!p){
			if(x <= 0){
				ST++;
				int pos = ST + n;
				if(cnt[pos])update(1 , 1 , lim , pos - cnt[pos] + 1 , pos , 1);
			}
			else{
				int pos = ST + n;
				if(cnt[pos])update(1 , 1 , lim , pos - cnt[pos] + 1 , pos , -1);
				ST--;
			}
		}
		else{
			if(a[p] <= ST + n)dec(a[p] , -1);
			else cnt[a[p]]--;
			a[p] = x + ST;
			if(a[p] <= ST + n)dec(a[p] , 1);
			else cnt[a[p]]++;
		}
		cout<<que(1 , 1 , lim , ST + 1 , ST + n)<<endl;
	}
}

学习了一下 区间求并的写法,之前自己的写法太拉胯了


这道题还有加强版,发现 原来题目的操作为 每次修改操作为单点修改或数列整体加一或数列整体减一。

加强版后的题目操作为 现在有一个长度为n的数列a和m次修改操作,每次修改形如“单点修改”或“数列数值整体平移”。每次操作后,你需要求出至少还要在数列中修改多少个数才能让数列变成合法的。

就是 上面那个整体+1,变成了区间 + k

需要用到一个 别的题的技巧 lgP4198 楼房重建 -》》》 加强up操作

考虑在 对于值域 维护一个 每一个位置往左的最远距离

之后就按代码里面的 up 时候\(O(logn)\)维护一下就好了

#include<bits/stdc++.h>
#define MAXN 5000005
#define INF 0x3f3f3f3f
using namespace std;

int ST = 150000 , lim = 450000 + 5;
int n,m,a[MAXN],cnt[MAXN],judge = 0;

struct node{int minl,sum,sum2;}t[MAXN * 5];

inline int gao(int rt , int l , int r , int v){
	if(l == r)return (min(v , t[rt].minl) <= l) ? 0 : 1;
	int mid = (l + r) >> 1;
	if(v <= mid)return gao(rt << 1 , l , mid , min(v , t[rt << 1 | 1].minl));
	else return t[rt].sum2 + gao(rt << 1 | 1 , mid + 1 , r , v);
}

inline void build(int rt , int l , int r){
	if(l == r)return (void)(t[rt].minl = INF , t[rt].sum = 1);	
	int mid = (l + r) >> 1;
	build(rt << 1 , l , mid);
	build(rt << 1 | 1 , mid + 1 , r);
	t[rt].sum2 = gao(rt << 1 , l , mid , t[rt << 1 | 1].minl);
	t[rt].sum = t[rt << 1 | 1].sum + t[rt].sum2;
	t[rt].minl = min(t[rt << 1].minl , t[rt << 1 | 1].minl);
}

inline void update(int rt , int l , int r , int x , int y){
	if(l == r)return (void)(t[rt].minl = y , t[rt].sum = (y > l) ? 1 : 0);	
	int mid = (l + r) >> 1;
	if(x <= mid)update(rt << 1 , l , mid , x , y);
	else update(rt << 1 | 1 , mid + 1 , r , x , y);
	t[rt].sum2 = gao(rt << 1 , l , mid , t[rt << 1 | 1].minl);
	t[rt].sum = t[rt << 1 | 1].sum + t[rt].sum2;
	t[rt].minl = min(t[rt << 1].minl , t[rt << 1 | 1].minl);
}

inline void dec(int x , int v){
	cnt[x + 150000] += v;
	update(1 , 1 , lim , x + 150000 , x + 150000 - cnt[x + 150000] + 1);
}

int minl = INF , zz_sum = 0;

namespace io {
	const int SIZE = 1 << 22 | 1;
	char iBuf[SIZE], *iS, *iT, c;
	char oBuf[SIZE], *oS = oBuf, *oT = oBuf + SIZE;
	#define gc() (iS == iT ? iT = iBuf + fread(iS = iBuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS++) : *iS++)
	template<class I> void read(I &x) {
		int f = 1;
		for(c = gc(); c < '0' || c > '9'; c = gc())
			if(c == '-') f = -1;
		for(x = 0; c >= '0' && c <= '9'; c = gc())
			x = (x << 3) + (x << 1) + (c & 15);
		x *= f;
	}
	inline void flush () {
		fwrite(oBuf, 1, oS - oBuf, stdout);
		oS = oBuf;
	}
	inline void putc(char x) {
		*oS++ = x;
		if(oS == oT) flush();
	}
	template<class I> void print(I x) {
		if(x < 0) putc('-'), x = -x;
		static char qu[55];
		char *tmp = qu;
		do *tmp++ = (x % 10) ^ '0'; while(x /= 10);
		while(tmp-- != qu) putc(*tmp);
		putc('\n');
	}
	struct flusher{ ~flusher() { flush(); } }_;
}

inline void que(int rt , int l , int r , int x , int y){
	if(r < x || l > y)return; 
	if(x <= l && r <= y){
		if(minl == INF){
			zz_sum += t[rt].sum;
			minl = t[rt].minl;
			return;
		}
		zz_sum += gao(rt , l , r , minl);
		minl = min(minl , t[rt].minl);
		return;
	}
	int mid = (l + r) >> 1;
	que(rt << 1 | 1 , mid + 1 , r , x , y);
	que(rt << 1 , l , mid , x , y);
} 

int main(){
	io :: read(n) , io :: read(m);
	for(int i = 1 ; i <= n ; i++){
		io :: read(a[i]) , cnt[a[i] + 150000]++;
	}
	build(1 , 1 , lim);
	for(int i = 1 ; i <= n ; i++){
		if(!cnt[i + 150000])continue;
		update(1 , 1 , lim , i + 150000 , i + 150000 - cnt[i + 150000] + 1);
	}
	int p,x,pp = 0;
	while(m--){
		io :: read(p);
		io :: read(x);
		if(!p)pp += x;
		else{
			dec(a[p] , -1);
			a[p] = x - pp;
			dec(a[p] , 1);
		}

		minl = INF , zz_sum = 0;
		que(1 , 1 , lim , ST + 1 - pp , ST + n - pp);
		io :: print(zz_sum);
		
	}
}

/*
3 9
1 2 3
0 1

每个节点都有一个往左扩展的区间长度 
*/
posted @ 2021-11-04 20:39  After_rain  阅读(52)  评论(0)    收藏  举报