神秘数
一道算是十分巧妙的题目。
与其它许多题目一样,一开始并不会想到用什么主席树,那些都只是后来用来做优化的东西。让我们来思考一下,假如给一个数列,然后只有一次询问的话应该怎么办?排一下序,然后从小到大扫描。从小往大扫描的过程中,如果当前可以表示的范围是 \([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;
}