神秘数 题解
这可以说是主席树的一个板子题,主要维护不同区间的不同值域的区间和。
我们设当前已经求出的区间为 \([1,x]\) ,那么对于能扩充这个区间的数一定符合 \(k\in[1,x+1]\) ,因为若 \(k>x+1\) 则 \(x+1\) 没有数来合成,所以答案即为 \(x+1\) 。
不过严谨得来说,设 \(lst\) 为上次合成的最右边界, \(k \ge lst+1\) ,否则一个数可能会重复用多次。
所以可以有两种写法
int lst=0,now=0;
while(1) {
int tmp=T.query(T.root[r],T.root[l-1],lst+1,now+1,1,sum);
if (tmp) lst=now+1,now+=tmp;
else break;
}
或者是
int now=0;
while(1) {
int tmp=T.query(T.root[r],T.root[l-1],1,now+1,1,sum);
if (tmp>now) now=tmp;
else break;
}
两种写法时间复杂度没有什么区别,都是 \(O(nlog_2(\sum_{i=1}^{n}a_i)+m(\text{区间和右界右移})log_2(\sum_{i=1}^{n}a_i))\)
但第二种常数会小很多,因为线段树区间求和,所求区间范围越大,分割的区间越少,所以常数会更小。实测真的会快不少。
\(AC \kern 0.4emCODE:\)
#include<bits/stdc++.h>
#define p(i) ++i
#define ri register int
using namespace std;
const int N=1e5+7;
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,num[N];
struct Seg{
int root[N],tot;
struct Segmenttree{
int l,r;
int sum;
}T[N<<5];
inline void up(int x) {T[x].sum=T[T[x].l].sum+T[T[x].r].sum;}
inline int New(int pre) {T[p(tot)]=T[pre];return tot;}
inline void insert(int &x,int pre,int l,int r,int p) {
x=New(pre);
if (l==r) {T[x].sum+=p;return;}
int mid=(l+r)>>1;
if (p<=mid) insert(T[x].l,T[pre].l,l,mid,p);
else insert(T[x].r,T[pre].r,mid+1,r,p);
up(x);
}
inline int query(int x,int y,int l,int r,int lt,int rt) {
if (T[x].sum==T[y].sum) return 0;
if (l<=lt&&rt<=r) return T[x].sum-T[y].sum;
int mid=(lt+rt)>>1,res=0;
if (l<=mid) res+=query(T[x].l,T[y].l,l,r,lt,mid);
if (r>mid) res+=query(T[x].r,T[y].r,l,r,mid+1,rt);
return res;
}
}T;
int main() {
int sum=0;
n=read();
for (ri i(1);i<=n;p(i)) sum+=(num[i]=read());
for (ri i(1);i<=n;p(i)) T.insert(T.root[i],T.root[i-1],1,sum,num[i]);
int q=read();
for (ri i(1);i<=q;p(i)) {
int l=read(),r=read();
int now=0;
while(1) {
int tmp=T.query(T.root[r],T.root[l-1],1,now+1,1,sum);
if (tmp>now) now=tmp;
else break;
}
printf("%d\n",now+1);
}
return 0;
}