首页 > 其他 > 详细

【回滚莫队】mex

时间:2021-05-11 16:24:29      阅读:18      评论:0      收藏:0      [点我收藏+]

题链

多次询问一个区间内最小没有出现过的自然数

回滚莫队:发现添加一个数难以维护,故只用删除操作来维护答案。用cnt[]来计数,一开始把[1,n]全部塞进去,记录全局答案ANS,只有新的询问所属的块与上次询问不同时更新ANS(因为该询问前面的块已经没有用了);

什么时候更新答案ans呢?一开始ans = ANS的,每一次删除的时候,判断cnt[x]是否等于0,因为一开始cnt[]数组是记录了 [ 询问左区间的块的左区间,n] 之间的所有数字的,所以删着删着如果cnt[x] == 0了,那么ans = min(ans,x);

详细看代码;

#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define ll long long
#define ULL unsigned long long
#define Pair pair<LL,LL>
#define f1 first
#define f2 second
#define ls rt<<1
#define rs rt<<1|1
#define Pi acos(-1.0)
#define eps 1e-6
#define DBINF 1e100
#define mod 998244353
#define MAXN 1e18
#define MS 1000009

LL n,m;
LL a[MS]; // 原数组 
struct node{
	int l,r,id;
}ask[MS];
LL size,bknum; // 每一块的位置,总块数 
LL bkl[MS],bkr[MS]; // 分别记录第i块的左右区间 
LL belong[MS]; // 表示下标pos位置所属块号 

LL ANS;
LL cnp[MS]; // 临时计数数组 用于暴力处理 cnp[i]表示i出现次数 
LL cnt[MS]; // 用于计数数组 cnt[i]表示i出现次数 

LL ac[MS]; // 记录答案 

void init_ANS(){
	for(int i=1;i<=n;i++){
		if(a[i] > n+1) continue;
		cnt[a[i]]++;
	}
	while(cnt[ANS]) ANS++;
}

void init_bk(){
	size = sqrt(n);
	bknum = n/size;
	for(int i=1;i<=bknum;i++){
		bkl[i] = (i-1)*size+1;
		bkr[i] = i*size; 
	}
	if(bkr[bknum] < n){
		bknum++;
		bkl[bknum] = bkr[bknum-1]+1;
		bkr[bknum] = n;
	}
	for(int i=1;i<=bknum;i++){
		for(int j=bkl[i];j<=bkr[i];j++){
			belong[j] = i;
		}
	}
}

bool cmp(node t1,node t2){ // 按块排序,左区间从小到大,右区间从大到小 
	if(belong[t1.l] != belong[t2.l]) return t1.l < t2.l;
	return t1.r > t2.r;
}

void add(int pos){ 
	if(a[pos] > n+1) return;
	cnt[a[pos]]++;
}

void remove(int pos,LL &ans){ // 删除时更新答案 
	if(a[pos] > n+1) return;
	cnt[a[pos]]--;
	if(cnt[a[pos]] == 0) ans = min(ans,a[pos]);
}

int main() {
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for(int i=1;i<=n;i++){
		cin >> a[i];
	}
	init_ANS(); // 初始化全局答案 
	for(int i=1;i<=m;i++){
		int l,r;
		cin >> l >> r;
		ask[i] = {l,r,i};
	}
	init_bk(); // 初始化 块 
	sort(ask+1,ask+m+1,cmp); // 对询问按 块 排序 
	
	int L = 1 ,R = n;
	int lastbk = 0;
	LL ans = ANS;
	for(int i=1;i<=m;i++){
		if(belong[ask[i].l] == belong[ask[i].r]){ // 属于同一块 暴力处理 
			for(int j=ask[i].l;j<=ask[i].r;j++){
				if(a[j] > n+1) continue;
				cnp[a[j]]++;
			}
			LL tmp = 0;
			while(cnp[tmp]) tmp++;
			ac[ask[i].id] = tmp;
			for(int j=ask[i].l;j<=ask[i].r;j++){
				if(a[j] > n+1) continue;
				cnp[a[j]]--;
			}
			continue;
		}
		if(belong[ask[i].l] != lastbk){ // 新的左区间与上次的左区间块不同 
			while(R < n) add(++R);
			while(L < bkl[belong[ask[i].l]]) remove(L++,ANS); // ***更新全局ANS 
			lastbk = belong[ask[i].l];
			ans = ANS;
		}
		while(R > ask[i].r){
			remove(R--,ans);
		}
		LL tmp = ans; // 左区间可能是乱序的,tmp作为临时记录 
		while(L < ask[i].l){
			remove(L++,tmp);
		}
		ac[ask[i].id] = tmp;
		// 回滚 
		while(L > bkl[belong[ask[i].l]]) add(--L);
	}
	for(int i=1;i<=m;i++){
		cout << ac[i] << "\n";
	}
	
	return 0;
}

【回滚莫队】mex

原文:https://www.cnblogs.com/Tecode/p/14753060.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!