题解:P17723 [SCP-S 2026] 函数 / func
看题解区都是双指针做法,于是我特此写了一篇线段树做法的题解,先别急着质疑时间复杂度,往下看。
解题思路
我们可以通过枚举右端点 + 按位计算贡献的方式求解。
对于每个右端点
先说结论,所有能让
证明
记
- 根据按位或的单调性:若
\operatorname{OR}(S(L,r)) 的第k 位为1 ,则对所有l \le L ,\operatorname{OR}(S(l,r)) 的第k 位一定为1 。区间越长,按位或的结果不会变小。 - 根据 MEX 的定义:若
\operatorname{MEX}(S(L,r)) 的第k 位为1 ,则对所有l \le L ,\operatorname { MEX } (S(l,r)) 的第k 位为1 或\operatorname{OR}(S(l,r)) 的第k 位为1 。 原因:当\operatorname{ MEX }(S(l,r)) \ne \operatorname{MEX}(S(L,r)) 时,说明原本的\operatorname{MEX}(S(L,r)) 已经出现在了更长的区间S(l,r) 中,此时\operatorname{OR}(S(l,r)) 的第k 位必然为1 。
综上,当
贡献计算
定义:
那么,
结合“只要其中一个条件满足,第
实现
由于
优化
普通线段树的单次区间查询复杂度为
但我们可以观察到:每次查询的区间恰好是
参考代码
#include <bits/stdc++.h>
using namespace std;
#define ls (i << 1)
#define rs (i << 1 | 1)
#define mid ((tl + tr) >> 1)
#define Max(a, b) ((a) = max((a), (b)))
using i32 = int;
using u64 = unsigned long long;
const i32 N = 2e6 + 5;
const i32 INF = 1e9;
const i32 SEG = 1048575; // 对应 2^20 - 1,线段树值域右端点
i32 n, x;
i32 R[31];
i32 id[N];
u64 ans;
i32 mi[N << 2];
void build(i32 tl, i32 tr, i32 i) {
if (tl == 0) id[tr] = i;
if (tl == tr) return;
build(tl, mid, ls);
build(mid + 1, tr, rs);
}
void upd(i32 tl, i32 tr, i32 pos, i32 val, i32 i) {
if (tl == tr) {
mi[i] = val;
return;
}
if (pos <= mid) upd(tl, mid, pos, val, ls);
else upd(mid + 1, tr, pos, val, rs);
mi[i] = min(mi[ls], mi[rs]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
i32 MAXN = 0;
build(0, SEG, 1);
cin >> n;
for (i32 j = 1; j <= n; ++j) {
cin >> x;
upd(0, SEG, x, j, 1);
for (i32 i = 0; i <= 30; ++i) {
if (x & (1 << i)) {
R[i] = j;
Max(MAXN, i + 1);
}
}
for (i32 i = 0; i <= MAXN; ++i) {
i32 t = 1 << i;
ans += (u64)max(R[i], (t <= SEG) ? mi[id[t - 1]] : 0) * t;
}
}
cout << ans << '\n';
return 0;
}