@description@

一个可重复数字集合 S 的神秘数定义为最小的不能被 S 的子集的和表示的正整数。例如:

S = {1,1,1,4,13}

1 = 1

2 = 1+1

3 = 1+1+1

4 = 4

5 = 4+1

6 = 4+1+1

7 = 4+1+1+1

8 无法表示为集合 S 的子集的和,故集合 S 的神秘数为 8。

现给定 n 个正整数 a1 ... an, m 个询问,每次询问给定一个区间 [l, r] (l <= r),求由 al ... ar 所构成的可重复数字集合的神秘数。

输入格式

第一行一个整数 n,表示数字个数。

第二行 n 个整数,从 1 编号。

第三行一个整数 m,表示询问个数。

以下 m 行,每行一对整数 l, r,表示一个询问。

输出格式

对于每个询问,输出一行对应的答案。

样例输入

5

1 2 4 9 10

5

1 1

1 2

1 3

1 4

1 5

样例输出

2

4

8

8

8

数据范围与提示

对于 100% 的数据点, n, m <= 100000,∑a = 10^9。

@solution@

不妨先看对于一个集合怎么快速求它的 “神秘数”。

众所周知子集和问题是 NPC 的,所以这个 “神秘数” 肯定是具有一定性质的。

我们考虑一个个加入元素。假如加入某一元素 x 时,此时的 “神秘数” 为 k。

如果用上 x 也无法拼出 k,则要么 x > k,要么 k - x 在加入 x 之前也无法被拼出。又因为 k 是最小的,只能说明 x > k。

如果我们从小到大考虑一个集合内的所有元素,且 x > k,则 x 以后也 > k。此时 “神秘数” 就保持不变了。

另一方面,假如 x <= k,则我们的 “神秘数” 将会从 k 变成 k + x。

这样我们就可以 O(n) 从小到大扫一遍,维护一个 “神秘数” k。

注意到 k 始终等于某个前缀的和 + 1,而这给了我们用数据结构维护的机会。

进一步地,假如套到原题上面来,我们可以莫队 + 维护一棵线段树,每个位置维护 \((\sum_{p=1}^{i-1}a_p) + 1 - a_i\)。然后线段树上二分求第一个负数的位置。

但是这还不够。复杂度 \(O(n\sqrt{n}\log n)\) 还是太慢了。

考虑复杂度卡在,我们每一次只能往集合里塞一个数。

假如说每一次当前所有 x <= k 的 x 全部塞进集合,会发生什么。

设前一次的 “神秘数” 为 p,则前一次我们可以把 <= p 的全部塞进集合,得到新一次的 “神秘数” 为 q。

新一轮中,我只能塞 p < x <= q 的数(太小的已经塞过了,太大的塞不进去)。假如能塞,则下一次的 “神秘数” >= 2*p。

于是这种操作下:“神秘数” 呈指数级增长。

那么对于每一个区间 [l, r],我们只需要找到 p < x <= q 的数的求和,得到新的 “神秘数”。

这个是可持久化线段树的经典应用了。我们按 ai 从小到大的顺序往线段树里塞,得到 <= ai 的 n 棵线段树。

@accepted code@

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef pair<int, int> pii;
#define mp make_pair
#define fi first
#define se second
const int MAXN = 100000;
struct node{
int sum;
node *ch[2];
}t[40*MAXN + 5], *rt[MAXN + 5], *ncnt, *NIL;
struct segtree{
segtree() {
NIL = ncnt = &t[0];
NIL->ch[0] = NIL->ch[1] = NIL;
NIL->sum = 0;
}
void pushup(node *x) {
x->sum = x->ch[0]->sum + x->ch[1]->sum;
}
node *newnode(const node *pre = NIL) {
ncnt++; *ncnt = *pre;
return ncnt++;
}
node *insert(const node *pre, int l, int r, int p, int k) {
node *x = newnode(pre); x->sum += k;
if( l == r ) return x;
int mid = (l + r) >> 1;
if( p <= mid ) x->ch[0] = insert(pre->ch[0], l, mid, p, k);
else x->ch[1] = insert(pre->ch[1], mid + 1, r, p, k);
pushup(x);
return x;
}
int query(const node *x, int l, int r, int ql, int qr) {
if( ql <= l && r <= qr )
return x->sum;
if( l > qr || r < ql )
return 0;
int mid = (l + r) >> 1;
return query(x->ch[0], l, mid, ql, qr) + query(x->ch[1], mid + 1, r, ql, qr);
}
}T;
int a[MAXN + 5]; pii b[MAXN + 5];
int main() {
int n, m; scanf("%d", &n);
for(int i=1;i<=n;i++)
scanf("%d", &a[i]);
scanf("%d", &m);
for(int i=1;i<=n;i++)
b[i] = mp(a[i], i);
sort(b + 1, b + n + 1);
rt[0] = NIL;
for(int i=1;i<=n;i++)
rt[i] = T.insert(rt[i - 1], 1, n, b[i].se, b[i].fi);
for(int i=1;i<=m;i++) {
int l, r, nw = 0, x; scanf("%d%d", &l, &r);
while( true ) {
x = T.query(rt[nw], 1, n, l, r) + 1;
int y = upper_bound(b + 1, b + n + 1, mp(x, n + 1)) - b - 1;
if( nw == y ) break;
nw = y;
}
printf("%d\n", x);
}
}

@details@

实现基本没有细节。

所以我太菜了。把正解思路擦边擦了个遍,就是没有想到正解的 “一次加入能加入的所有数”。

这本来是道 sb 题,可惜我太 sb,切不出来。

最新文章

  1. Mac OS X上编写 ASP.NET vNext(一)KRE环境搭建
  2. 十五分钟学会用Hessian
  3. zeromq:c,c++,golang及nodejs使用
  4. Semaphore(信号量)
  5. 有些网站为什么要使用CDN,CDN又是什么呢
  6. ASP.NET中进行消息处理(MSMQ) 二
  7. MVC 学习系列-Controller
  8. 社交分享:-canOpenURL: failed for URL: &quot;weixin://app/*************/&quot; - error: &quot;This app is not allowed to query for scheme weixin&quot;
  9. Super Hide IP 3.4.7.8允许您以匿名方式进行网上冲浪、 保持隐藏您的 IP 地址
  10. 制作PPT时,可能这些小习惯你需要注意
  11. 【Java】WebService教程
  12. JAVA为什么会空指针异常
  13. information_schema.columns 学习
  14. mysql自定义循环函数
  15. javaWEB总结(16):jsp错误页面的处理
  16. Js中数据类型判断的几种方法
  17. 【JVM命令系列】jstat
  18. oracle状态
  19. HTML之事件处理程序
  20. Python 正则表达式 (python网络爬虫)

热门文章

  1. kindle电子书下载网站收藏
  2. 八大 IoT 安全关键技术解析
  3. android 数据库存取图片
  4. memcache缓存使用详解
  5. python 处理缺失值
  6. 关于Layui 响应式移动端轮播图的问题
  7. pycharm 测试执行成功,但却无法成功生成测试报告(使用HTMLTestRunner)的解决办法
  8. https比http到底那里安全?
  9. LeetCode169 Majority Element, LintCode47 Majority Number II, LeetCode229 Majority Element II, LintCode48 Majority Number III
  10. text()和html()区别