[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;
		}
	}
}
posted @ 2020-08-06 20:51  leiyuanze  阅读(108)  评论(0编辑  收藏  举报