题解:P4587 [FJOI2016] 神秘数
考虑给定一个集合
我们维护这样一个过程:设置一个变量
-
若
x > \text{now} + 1 ,因为x 已经是此时S 中最小的数,所以\text{now} + 1 这个数无论如何都无法被表示,直接退出。 -
若
x \le \text{now} + 1 ,考虑如果选了x ,因为原本[0, \text{now}] 中的数都能被表示,所以现在[x, x + \text{now}] 中的数也能被表示了,于是现在能表示的数的范围是[0, x + \text{now}] 。相当于做\text{now} \gets \text{now} + x ,并继续考虑下一个x 。
最终
考虑将序列
这样段数显然只有
-
如果
x \le \text{now} + 1 ,那么做\text{now}' \gets \text{now} + x ,此时考虑段中的下一个数x' ,由于x' < 2^{k + 1} - 1 ,一定有x' \le \text{now}' + 1 = \text{now} + x + 1 。这说明若[2^k, 2^{k + 1} - 1) 中的任意一个数可以加入\text{now} ,则所有数都可以加入\text{now} 。 -
如果
x > \text{now} + 1 ,那么没有数可以加入\text{now} 。
于是只要维护:如果
考虑将询问离线,从小到大扫描
对于 RMQ 的部分,朴素实现会带
总时间复杂度
#include <bits/stdc++.h>
using namespace std;
const int kN = 1e5 + 5;
const int M = 17;
int n, m, len;
int a[kN], s[kN], pre[kN], nxt[kN], ls[kN], id[kN], st[kN][M];
int l[kN], r[kN], now[kN];
int Query_min (int l, int r) {
int k = log2(r - l + 1);
return min(st[l][k], st[r - (1 << k) + 1][k]);
}
int main () {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i];
cin >> m;
for (int i = 1; i <= m; ++i) cin >> l[i] >> r[i];
for (int w = 0; w < 30; ++w) {
nxt[n + 1] = n + 1, len = 0;
for (int i = 1; i <= n; ++i) {
if ((1 << w) <= a[i] && a[i] < (1 << (w + 1))) {
pre[i] = i;
s[i] = s[i - 1] + a[i];
ls[++len] = i;
id[i] = len;
st[len][0] = a[i];
}
else {
pre[i] = pre[i - 1];
s[i] = s[i - 1];
}
}
for (int i = n; i; --i) {
if (pre[i] == i) nxt[i] = i;
else nxt[i] = nxt[i + 1];
}
for (int k = 1; k <= log2(len); ++k) {
for (int i = 1; i + (1 << k) - 1 <= len; ++i) {
st[i][k] = min(st[i][k - 1], st[i + (1 << (k - 1))][k - 1]);
}
}
for (int i = 1; i <= m; ++i) {
int l = nxt[::l[i]], r = pre[::r[i]];
if (l <= r && Query_min(id[l], id[r]) <= now[i] + 1) {
now[i] += s[r] - s[l - 1];
}
}
}
for (int i = 1; i <= m; ++i) {
cout << now[i] + 1 << '\n';
}
return 0;
}