[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
" , ans); break;}
else ans = s + 1;
}
}
}