P4137 Rmq Problem / mex 题解

前言

好久没用整体二分发电了。

正文

注意到实际上 mex 是可以二分的。

对于全局 mex 我们可以二分 mex 是哪个值。

check 为是否区间内比 xxx(假设当前二分答案为 xxx)小的个数是否等于 x+1x+1x+1 是则记录答案为 x+1x+1x+1l=mid+1l=mid+1l=mid+1;不是则 r=mid−1r=mid-1r=mid1

这是显然正确的吧。

那么对于区间查询 mex,只需要更改 check,改为满足该条件下的 dp 的 fxf_xfx 数值是否等于 x+1x+1x+1

条件:

  1. ai<aja_i < a_jai<aj
  2. L⩽i⩽RL \leqslant i \leqslant RLiR
  3. L⩽j⩽RL \leqslant j \leqslant RLjR
  4. aj⩽xa_j \leqslant xajx

其中 LLLRRR 为查询区间。

注意到可以使用类似于扫描线的方法,询问和插入均按照右端点或下标从小到大排序,之后用整体二分二分 xxx 每个能做贡献的数值只保留与其相同数值中下表最大的那一个放进树状数组,之后查询是否满足要求,就完了。

可能会有一些细节但不多。

代码

#include<bits/stdc++.h>
#define lowbit(x) ((x)&(-x))
#define N 1000005
using namespace std ;
int n , q , tr[N] , last[N] , ans[N] , cnt=0 , num=0 ;
void change(int x,int k) {
	while(x<=n+1) {
		tr[x] += k ;
		x += lowbit(x) ;
	}
}
int ask(int x) {
	int ans=0 ;
	while(x) {
		ans += tr[x] ;
		x -= lowbit(x) ;
	}
	return ans ;
}
struct node {
	int x , i , l , r , opt , k , tm ;
	bool operator <(const node &a)const {
		return this->tm>a.tm ;
	}
} a[N],q1[N],q2[N];
void solve(int l,int r,int L,int R) {
	if(l>r||L>R) return ;
	int mid=(l+r)>>1 , tot1=0 , tot2=0 ;
	for(int i=L; i<=R; i++) {
		if(a[i].opt==1) {
			if(a[i].x<=mid){
				if(last[a[i].x]) change(last[a[i].x],-1) ;
				last[a[i].x] = a[i].i ;
				change(a[i].i,1) ;
				q1[++tot1] = a[i] ;
			} else q2[++tot2] = a[i] ;
		} else {
			int x=ask(a[i].r)-ask(a[i].l-1) ;
			if(a[i].k+x<=mid) q1[++tot1] = a[i] ;
			else if(x+a[i].k==mid+1) {
				ans[a[i].i] = mid+1 ;
				a[i].k += x ;
				q2[++tot2] = a[i] ;
			} else exit(114514) ;
		}
	}
	for(int i=1; i<=tot1; i++) {
		a[i+L-1] = q1[i] ;
		if(q1[i].opt==1) {
			if(last[q1[i].x]) {
				change(last[q1[i].x],-1) ;
				last[q1[i].x] = 0 ;
			}
		}
	}
	for(int i=1; i<=tot2; i++) a[i+L-1+tot1] = q2[i] ;
	if(l!=r) solve(l,mid,L,L+tot1-1) ;
	solve(mid+1,r,L+tot1,R) ;
}
int read() {
	int x=0 ;
	char a=getchar() ;
	while(!(a>='0'&&a<='9')) a = getchar() ;
	while(a>='0'&&a<='9') {
		x *= 10 ;
		x += a-'0' ;
		a = getchar() ;
	}
	return x ;
}
bool cmp(node a,node b) {
	return a.r==b.r?a.tm<b.tm:a.r<b.r ;
}
int t[N] ;
signed main(){
	n = read() , q = read() ;
	for(int i=1; i<=n; i++) {
		int temp=read() ;
		cnt++ ;
		a[cnt].opt = 1 , a[cnt].k = 1 , a[cnt].x = temp , a[cnt].r = a[cnt].i = i , a[cnt].tm = cnt ;
	}
	for(int i=1; i<=q; i++) {
		int x , y ;
		x = read() , y = read() ;
		cnt++ ;
		a[cnt].i = ++num , a[cnt].opt = 2 , a[cnt].l = x , a[cnt].r = y , a[cnt].tm=cnt ;
	}
	sort(a+1,a+1+cnt,cmp) ;
	solve(0,n+1,1,cnt) ;
	for(int i=1; i<=num; i++) printf("%d\n",ans[i]) ;
	return 0 ;
}

废话

本人模拟赛做该题的待修版(单点),把 check 推成了等于 xxx 就行,实际上这道题的样例已经否定了这种写法,希望大家不要像我一样犯这种高级错误(实际上还有 multiset 用 end 查最大值这种发电操作)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值