【题解】 「FJOI2016」神秘数 线段树+倍增+离线 LOJ2174
Legend
Link \(\textrm{to LOJ}\)。
一个可重复数字集合 \(S\) 的神秘数定义为最小的不能被 \(S\) 的子集的和表示的正整数。
给定长 \(n\) 的正整数序列 \(a_i\),\(q\) 个询问,每次询问给定一个区间 \([l,r]\),求由 \(a_l,a_{l+1},\cdots,a_r\) 所构成的可重复数字集合的神秘数。
\(n \le 10^5,\sum a_i \le 10^9\)。
Editorial
这题还挺有意思的。
首先考虑最暴力的做法:把区间提取出来 \(\rm{sort}\) 一遍。
然后从小到大贪心,如果当前能表示出 \(s\),下一个数字是 \(x\)。
- 若 \(x>s+1\),答案就是 \(s\)。
- 若 \(x \le s+1\),令 \(s \gets s + x\),重复上述操作。
如何优化暴力呢?想到以 \(2^k\) 为长度倍增。
给 \(a_j \in [2^i,2^{i+1})\) 的数字开同一棵线段树(下标是在原数组中的位置),维护区间最小值(没有数字认为是 \(\infty\))。
这样子对值域进行倍增,则当前的 \(s+1\) 总会落在恰好一个长度区间内,假设是 \([2^k,2^{k+1})\)。
设询问是 \([l,r]\) 则检查值域为 \([2^k,2^{k+1})\) 的线段树里下标 \([l,r]\) 的最小值是否 \(\le s+1\)。
如果 \(\le s+ 1\),那么这一棵线段树里的所有值都可以被拿走,加入到 \(s\) 里,因为拿走最小值之后 \(s+1\) 一定到了下一个值域区间。
反之 \(s\) 不能再被更新了,当前 \(s\) 即为答案。
所以复杂度即为 \(O(n \log n \log \sum a)\)。
Code
人丑常数大呀。。。
最开始开了 \(30\) 个线段树果断 \(\rm{MLE}\),于是就把询问全部离线下来做就 OK 了。
好像暴力重构线段树比较慢,\(\rm luogu\) 上卡着 \(\rm{1s}\) 时限过的。
#include <bits/stdc++.h>
#define LL long long
int read() {
char k = getchar(); int x = 0;
while(k < '0' || k > '9') k = getchar();
while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
return x;
}
const int MX = 1e5 + 23;
struct node {
int l, r, mn;
node *lch, *rch;
}*root;
void pushup(node *x) {
x->mn = std::min(x->lch->mn, x->rch->mn);
}
int S[32][MX];
node *build(int l, int r ,int id) {
node *x = new node;
x->l = l ,x->r = r;
if(l == r) {
x->mn = S[id][l] ? S[id][l] : INT_MAX;
x->lch = x->rch = nullptr;
}
else {
int mid = (l + r) >> 1;
x->lch = build(l, mid, id);
x->rch = build(mid + 1, r, id);
pushup(x);
}
return x;
}
void fresh(node *x, int id){
if(x->l == x->r) {
x->mn = S[id][x->l] ? S[id][x->l] : INT_MAX;
}
else {
fresh(x->lch, id);
fresh(x->rch, id);
pushup(x);
}
}
int Qmin(node *x, int l, int r) {
if(l <= x->l && x->r <= r) return x->mn;
if(l <= x->lch->r && x->lch->r < r)
return std::min(Qmin(x->lch, l, r), Qmin(x->rch, l, r));
if(l <= x->lch->r) return Qmin(x->lch, l, r);
return Qmin(x->rch, l, r);
}
struct QUERY{
int l ,r;
int Ans;
}Q[MX];
int main() {
int n = read();
for(int i = 1; i <= n; ++i) {
S[0][i] = read();
}
int m = read();
for(int i = 1; i <= m; ++i) {
Q[i].l = read(), Q[i].r = read();
Q[i].Ans = 0;
}
root = build(1 ,n ,1);
for(int i = 1; i <= 30; ++i) {
for(int j = 1; j <= n; ++j) {
if((1 << (i - 1)) <= S[0][j] && S[0][j] < (1 << i)) {
S[i][j] = S[0][j];
}
else {
S[i][j] = 0;
}
}
fresh(root ,i);
for(int j = 1; j <= n; ++j) {
S[i][j] += S[i][j - 1];
}
for(int j = 1; j <= m; ++j) {
if(Q[j].Ans + 1 < (1 << (i - 1))) {
continue;
}
if(Qmin(root ,Q[j].l ,Q[j].r) <= Q[j].Ans + 1) {
Q[j].Ans += S[i][Q[j].r] - S[i][Q[j].l - 1];
}
}
}
for(int i = 1; i <= m; ++i){
printf("%d\n" ,Q[i].Ans + 1);
}
return 0;
}