测试地址:神秘数
题目大意:定义一个集合的神秘数,为不能用其某个子集的和表示的正整数中最小的数,给定一个长为
n
n
的序列,
m
m
次询问,每次询问某个区间的神秘数。。
做法:本题需要用到主席树+贪心。
考虑只有一个询问时要怎么做。首先,将
Ai
A
i
从小到大一个个加入集合,那么在某个时刻,能用集合的某个子集表示的数的集合在数轴上就不是连续的了,我们知道这时候,最小的断点就是我们要求的神秘数。为什么呢?令在这个时刻之前,能用它们表示的数的集合为
1
1
~,加上
Ai
A
i
后,能表示的数的集合为
1
1
~和
Ai
A
i
~
x+Ai
x
+
A
i
的并集,如果
Ai>x+1
A
i
>
x
+
1
,就会产生
x+1
x
+
1
这个断点,而因为对于任意
j>i
j
>
i
,有
Aj≥Ai
A
j
≥
A
i
,所以无论如何都拼不出
x+1
x
+
1
来了,于是这个
x+1
x
+
1
就是集合的神秘数。
注意到这样做一次最多是
O(n)
O
(
n
)
的,考虑优化。因为我们要求第一个
Ai
A
i
使得
Ai>1+∑i−1j=1Aj
A
i
>
1
+
∑
j
=
1
i
−
1
A
j
,每当我们添加进一个数
Ai
A
i
,答案一定会大于
∑ni=1Ai
∑
i
=
1
n
A
i
,这样我们只需找到比这个大的第一个
Ai
A
i
进行下一步即可,而中间的数可以直接累加进答案里。可以证明这样只会进行
O(log∑Ai)
O
(
log
∑
A
i
)
步,简单证明如下:设某一步的前缀和为
x
x
,那么下一步我们至少会加上比大的最小的
Ai
A
i
,这样总和会成倍增长,所以复杂度为上面所述。
注意到,上面询问中,寻找序列区间中比某数大的第一个数,询问序列区间中权值在某一段内的数的和,这两个显然是主席树的基本操作,因此用主席树维护,时间复杂度为
O(mlognlog∑Ai)
O
(
m
log
n
log
∑
A
i
)
。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,tmp,a[100010],pos[100010],rt[100010];
int seg[2000010]={0},ch[2000010][2]={0},tot=0;
struct forsort
{
int id,val;
}f[100010];
bool cmp(forsort a,forsort b)
{
return a.val<b.val;
}
void buildtree(int &no,int l,int r)
{
no=++tot;
if (l==r) return;
int mid=(l+r)>>1;
buildtree(ch[no][0],l,mid);
buildtree(ch[no][1],mid+1,r);
}
void insert(int &no,int last,int l,int r,int x)
{
no=++tot;
seg[no]=seg[last];
ch[no][0]=ch[last][0];
ch[no][1]=ch[last][1];
if (l==r) {seg[no]+=pos[x];return;}
int mid=(l+r)>>1;
if (x<=mid) insert(ch[no][0],ch[last][0],l,mid,x);
else insert(ch[no][1],ch[last][1],mid+1,r,x);
seg[no]=seg[ch[no][0]]+seg[ch[no][1]];
}
int query(int no,int last,int x)
{
int s=0,l=1,r=tmp;
while(l<r)
{
int mid=(l+r)>>1;
if (pos[mid]<=x)
{
s+=seg[ch[no][0]]-seg[ch[last][0]];
no=ch[no][1];
last=ch[last][1];
l=mid+1;
}
else
{
no=ch[no][0];
last=ch[last][0];
r=mid;
}
}
if (pos[l]<=x) s+=seg[no]-seg[last];
return s;
}
int find(int no,int last,int l,int r,int x)
{
if (seg[no]-seg[last]==0) return -1;
if (pos[r]<=x) return -1;
if (l==r) return l;
int mid=(l+r)>>1,ans=-1;
if (x<pos[mid]) ans=find(ch[no][0],ch[last][0],l,mid,x);
if (ans==-1) ans=find(ch[no][1],ch[last][1],mid+1,r,x);
return ans;
}
int solve(int L,int R)
{
int x=0;
while(true)
{
int s,nxt;
s=query(rt[R],rt[L-1],x);
nxt=find(rt[R],rt[L-1],1,tmp,x);
x=s;
if (nxt==-1||pos[nxt]>x+1) break;
x+=pos[nxt];
}
return x+1;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&f[i].val);
f[i].id=i;
}
sort(f+1,f+n+1,cmp);
tmp=0;
for(int i=1;i<=n;i++)
{
if (i==1||f[i].val!=f[i-1].val)
pos[++tmp]=f[i].val;
a[f[i].id]=tmp;
}
buildtree(rt[0],1,tmp);
for(int i=1;i<=n;i++)
insert(rt[i],rt[i-1],1,tmp,a[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",solve(l,r));
}
return 0;
}