主席树Ⅱ

神秘数

一道算是十分巧妙的题目。

与其它许多题目一样,一开始并不会想到用什么主席树,那些都只是后来用来做优化的东西。让我们来思考一下,假如给一个数列,然后只有一次询问的话应该怎么办?排一下序,然后从小到大扫描。从小往大扫描的过程中,如果当前可以表示的范围是 \([1,able]\) ,当前这个可利用的数是 \(now\) ,那么很容易可以得到,如果 \(able+1\)<\(now-1\) 的话加入一个新的数必然会导致 \([able+1,now-1]\) 的数无法表示,反之,就把 \(able\) 修改成 \(able+now\) 。然后就可以啦。

但这样似乎是 \(O(NlogN)\) 的算法,如果硬来的话,势必会导致算法成为 \(MNlogN\) 完全过不了。所以我们需要加速这个过程。一般来说,导致这种情况的根源就是它指针是一个一个往后挪的,这非常浪费时间(想起了 \(KMP\) 算法……),我们就可以想一下,如何加速这个过程。

上面也说了,一个元素 \(now\) 可以更新 \(able\) 的条件是 \(able+1\)<\(now-1\) ,也就是说 \(\forall now\in[1,able+1]\) 都可以更新 \(able\) ,相当于每次我们进行的操作就变成了 \(able=able+\sum{now\in[last,able+1]}\),其中 \(last\) 表示上一次的 \(able\) 这样是为了防止同一个元素被加很多次。

可证这样的算法是 \(MlogN^2\) 的。但这还只是静态问题,我们需要进行区间查询,而这种比较复杂的区间信息查询显然无法用线段树实现,要么用分块,要么用莫队离线求,要么用主席树,这里选用了主席树。然后就好办啦。

#include<cstdio>
//#define zczc
const int N=100010;
const int S=1e9;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
    wh*=f;return;
}

#define lc t[wh].left
#define rc t[wh].right
#define mid (l+r>>1)
struct node{
	int left,right,data;
}t[N<<5];
int nodecnt;
inline void pushup(int wh){
	t[wh].data=t[lc].data+t[rc].data;
	return;
}
inline int change(int x,int l,int r,int pl){
	
	int wh=++nodecnt;
	lc=t[x].left,rc=t[x].right,t[wh].data=t[x].data;
	t[wh].data+=pl;
	if(l==r){
		return wh;
	}
	if(pl<=mid)lc=change(lc,l,mid,pl);
	else rc=change(rc,mid+1,r,pl);
	//pushup(wh);
	
	//printf("c:%d %d %d %d %d %d\n",x,wh,l,r,pl,t[wh].data);
	
	return wh;
}
inline int work(int lwh,int rwh,int l,int r,int wl,int wr){
	//printf("%d %d %d %d %d %d\n",lwh,rwh,l,r,wl,wr);
	if(wl>wr)return 0;
	if(t[rwh].data-t[lwh].data==0)return 0;
	if(wl<=l&&r<=wr)return t[rwh].data-t[lwh].data;
	int an=0;
	if(wl<=mid)an+=work(t[lwh].left,t[rwh].left,l,mid,wl,wr);
	if(wr>mid)an+=work(t[lwh].right,t[rwh].right,mid+1,r,wl,wr);
	return an;
}
#undef mid
#undef lc
#undef rc

int root[N],m,n;

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	
	int s1,s2;
	read(m);
	for(int i=1;i<=m;i++){
		read(s1);
		root[i]=change(root[i-1],1,S,s1);
		//printf("now:%d\n",root[i]);
	}
	read(n);
	while(n--){
		read(s1);read(s2);
		int maxn=0,ans=0,sum=0;
		while(true){
			sum=work(root[s1-1],root[s2],1,S,maxn+1,ans+1);
			//printf("work:%d %d %d\n",maxn+1,ans+1,sum);
			if(sum==0)break;
			maxn=ans+1;ans+=sum;
		}
		printf("%d\n",ans+1);
	}
	
	return 0;
}
posted @ 2021-08-18 21:23  Feyn618  阅读(38)  评论(0编辑  收藏  举报