1. 程式人生 > 實用技巧 >[FJOI2016]神祕數

[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;
		}
	}
}