BZOJ4408: [Fjoi 2016]神秘数

BZOJ

题意

给你\(n\)个数\(A_i...A_n\),每次询问一个区间\([l,r]\),问这个区间中的数不能组成的最小正整数是多少;

题解

遇到这种题询问的题一般先考虑如果只询问一次,一次询问整个数列怎么做;
考虑把数从小到大排序,然后求出前缀和\(S_1...S_n\)接下来有一个比较显然但是非常关键的结论:不能表示的最小的正整数,一定是某个位置的前缀和+1(包含\(0\)\(n\));设\(F[i]=[1,0]\)表示小于等于\(i\)的正整数能否全部被表示出来,那么我们要找的就是这么一个位置:最小的\(j\)使得\(F[A_j]=0\),那么不能表示出的最小正整数便是\(S_{j-1}+1\);考虑如果某个位置\(t\),\(F[A_t]=1\),那么\(S_t\)一定大于等于\(A_t\),那么这前\(t\)个数一定能够把\(1,2,3..S_t\)全部表示出来(具体证明只需要把数拆成二进制位看看就很清楚了),那么转移就有了两种情况,如果\(A_{t+1}>S_t+1\)\(F[A_{t+1}]=0\),否则\(F[A_{t+1}]=1\);知道了转移我们便有了一种很快找到这个位置的办法了,考虑初始能表示的数为\(s\),\(s\)一开始等于\(0\),然后接下来小于等于\(s+1\)的数全部能被表示出来,找到最多能扩展到的位置\(p\),那么现在全部能被表示出来的数便成了\(S_p\)(前缀和),这样每次去扩展直到扩展不动,最终的答案便是能扩展到的位置的前缀和+1;因为每次会出现一个新的最小的能被表示的数至少比已知能扩展的大\(1\),那么最坏情况也只会扩展\(log\)次(考虑序列\(1,2,4,8,16..\)每次只扩展一个位置,但因为题目条件所有数总和小于等于\(1e9\),所以最多扩展30次也就能全部扩展完了);
然后是区间查询,其实我们需要的也就是查区间中有多少个数小于等于某数,上主席树就做完了;

#include<bits/stdc++.h>
#define Fst first
#define Snd second
#define RG register
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
typedef long double LD;
typedef unsigned int UI;
typedef unsigned long long ULL;
template<typename T> inline void read(T& x) {
	char c = getchar();
	bool f = false;
	for (x = 0; !isdigit(c); c = getchar()) {
		if (c == '-') {
			f = true;
		}
	}
	for (; isdigit(c); c = getchar()) {
		x = x * 10 + c - '0';
	}
	if (f) {
		x = -x;
	}
}
template<typename T, typename... U> inline void read(T& x, U& ... y) {
	read(x), read(y...);
}
const int N=1e5+10;
int n,Q,SUM,CNT,size;
int A[N],V[N],root[N];
map<int,int> S;
struct Node {
	int lo,ro,cnt,sum;
}Tr[N*20];
void Modify(int l,int r,int &o,int pos,int v) {
	Tr[++size]=Tr[o]; o=size;
	++Tr[o].cnt; Tr[o].sum+=v;
	if(l==r) return;
	int mid=l+r>>1;
	if(pos<=mid) Modify(l,mid,Tr[o].lo,pos,v);
	else Modify(mid+1,r,Tr[o].ro,pos,v);
}
void Find(int l,int r,int x,int y,int sum) {
	if(l==r) {
		if(V[l]<=sum) SUM+=Tr[x].sum-Tr[y].sum,CNT+=Tr[x].cnt-Tr[y].cnt;
		return;
	}
	int mid=l+r>>1;
	if(V[mid]<=sum) SUM+=Tr[Tr[x].lo].sum-Tr[Tr[y].lo].sum,CNT+=Tr[Tr[x].lo].cnt-Tr[Tr[y].lo].cnt,Find(mid+1,r,Tr[x].ro,Tr[y].ro,sum);
	else Find(l,mid,Tr[x].lo,Tr[y].lo,sum);
}
int main() {
//	ios::sync_with_stdio(false);
#ifdef rua
	freopen("GG.in","r",stdin);
#endif
	read(n);
	for(int i=1;i<=n;++i) read(A[i]),V[i]=A[i];
	sort(V+1,V+n+1);
	int cnt=unique(V+1,V+n+1)-V-1;
	for(int i=1;i<=cnt;++i) S[V[i]]=i;
	for(int i=1;i<=n;++i) Modify(1,cnt,root[i]=root[i-1],S[A[i]],A[i]);
	read(Q);
	while(Q--) {
		int l,r; read(l,r);
		int last=0,sum=0;
		while(1) {
			CNT=SUM=0; Find(1,cnt,root[r],root[l-1],sum+1);
			if(CNT==last) break;
			last=CNT; sum=SUM;
		}
		printf("%d\n",sum+1);
	}
	return 0;
}

posted @ 2019-01-04 19:42  Sachiko  阅读(128)  评论(0编辑  收藏  举报