[FJOI2016]神秘数
[FJOI2016]神秘数
题目
仍然自己上网搜~~~~~~
思路
不得不说这题很~~~
规律,永远是宇宙的终极杀器
我们考虑当前的集合可以表示的数的值域为 \([1..Max]\)
这段区间是连续的
为什么我们只考虑从 \(1\) 开始连续的区间?
因为这样我们可以知道答案显然为 \(Max + 1\)
因为 \(Max + 1\) 是最小的不能被表示的数
······
废话!!!
其实我们可以反过来想,如果我猜想当前答案可能为 \(ans\)
然后验证它是不是答案
那么当前集合我们是不需要考虑比 \(ans\) 大的数的
也就是说当前集合比 \(ans\) 小的数能拼成的所有数中有没有 \(ans\) 就是判定关键
可是我怎么知道它怎么能拼出!!!
如果它能拼出的数的区间是连续的那就好了
其实我们注意到,它的区间即使不是连续的,也分成了几块连续的段
那么我们可以从小(即 \(1\))到大猜一个 \(ans\)
然后查询区间中小于等于 \(ans\) 的数之和(注:因为这个区间能表示的数是连续的,所以上限是这些数之和,判上限就好了)
若这个和为 \(s\)
如果 \(s \leq ans\) ,那么 \(ans\) 不能被表示,因为从小到大,所以它就是答案
否则,我们得重新猜 \(ans\)
\(ans = s + 1\) ?!!!
这样时间就有保证了
\(O(m \log n \log{\sum a_i})\)
\(Code\)
#include<cstdio>
using namespace std;
const int N = 1e5 , Len = 1e9;
int n , m , a[N + 5] , rt[N + 5] , size;
struct segment{
int ls , rs , sum;
}seg[(N << 5) + 5];
inline int update(int x , int l , int r , int v)
{
int o = ++size;
seg[o] = seg[x] , seg[o].sum += v;
if (l == r) return o;
int mid = (l + r) >> 1;
if (v <= mid) seg[o].ls = update(seg[x].ls , l , mid , v);
else seg[o].rs = update(seg[x].rs , mid + 1 , r , v);
return o;
}
inline int query(int u , int v , int l , int r , int val)
{
if (r <= val) return seg[v].sum - seg[u].sum;
int mid = (l + r) >> 1 , res = 0;
res += query(seg[u].ls , seg[v].ls , l , mid , val);
if (val > mid) res += query(seg[u].rs , seg[v].rs , mid + 1 , r , val);
return res;
}
int main()
{
scanf("%d" , &n);
for(register int i = 1; i <= n; i++) scanf("%d" , &a[i]) , rt[i] = update(rt[i - 1] , 1 , Len , a[i]);
scanf("%d" , &m);
int l , r;
for(; m; m--)
{
scanf("%d%d" , &l , &r);
int ans = 1 , s = 0;
while (1)
{
s = query(rt[l - 1] , rt[r] , 1 , Len , ans);
if (s < ans) {printf("%d\n" , ans); break;}
else ans = s + 1;
}
}
}