LUOGU P4587 [FJOI2016]神秘数(主席树)

传送门

解题思路

  如果区间内没有\(1\),那么答案就为\(1\),从这一点继续归纳。如果区间内有\(x\)\(1\),设区间内\([2,x+1]\)的和为\(sum\),如果\(sum=0\),那么答案为\(x+1\),否则\([1,x+sum]\)中的所有数字一定可以被表示,然后这个操作每次使答案至少扩大\(1\)倍,再用一个主席树维护,时间复杂度\(O(nlognlogA)\)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>

using namespace std;
const int N=100005;
const int M=N*33;
const int inf=1000000000;
typedef long long LL;

template<class T> void rd(T &x){
	x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
}	

int n,m,a[N],rt[N],ls[M],rs[M],sum[M],cnt;
LL Sum[M];

void build(int &x,int l,int r,int k){
	x=++cnt;
	if(l==r) {sum[x]=1;Sum[x]=l;return ;}
	int mid=(l+r)>>1;
	if(k<=mid) build(ls[x],l,mid,k);
	else build(rs[x],mid+1,r,k);
	Sum[x]=Sum[ls[x]]+Sum[rs[x]];
}

void update(int pre,int &x,int l,int r,int k){
	x=++cnt;ls[x]=ls[pre];rs[x]=rs[pre];
	if(l==r) {sum[x]=sum[pre]+1;Sum[x]=Sum[pre]+l;return;}int mid=(l+r)>>1;
	if(k<=mid) update(ls[pre],ls[x],l,mid,k);
	else update(rs[pre],rs[x],mid+1,r,k);
	Sum[x]=Sum[ls[x]]+Sum[rs[x]];
}

int query_tot(int u,int v,int l,int r,int k){
	if(l==r) return sum[v]-sum[u];
	int mid=(l+r)>>1;
	if(k<=mid) return query_tot(ls[u],ls[v],l,mid,k);
	else return query_tot(rs[u],rs[v],mid+1,r,k);	
}

LL query_sum(int u,int v,int l,int r,int L,int R){
	if(L<=l && r<=R) return Sum[v]-Sum[u];
	int mid=(l+r)>>1;LL ret=0;
	if(L<=mid) ret+=query_sum(ls[u],ls[v],l,mid,L,R);
	if(mid<R) ret+=query_sum(rs[u],rs[v],mid+1,r,L,R);
	return ret;
}

int main(){
	rd(n);rd(a[1]);build(rt[1],1,inf,a[1]);
	for(int i=2;i<=n;i++)
		rd(a[i]),update(rt[i-1],rt[i],1,inf,a[i]);
	rd(m);int l,r,now,k,tot,lst;
	while(m--){
		rd(l),rd(r);k=0;lst=0;
		while(1){
			now=query_sum(rt[l-1],rt[r],1,inf,lst,k+1);
			if(!now) break;lst=k+2;k=now+k;
		}
		printf("%d\n",k+1);
	}
	return 0;
}

posted @ 2019-01-11 11:23  Monster_Qi  阅读(110)  评论(0编辑  收藏  举报