[FJOI2016]神祕數
阿新 • • 發佈:2020-08-06
[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; } } }