P3924 康娜的线段树
首先考虑每一个 \(a_i\)(叶子结点)对于总期望值有多少贡献。不妨以区间 \([1,8]\) 建线段树,看看 \(a_i=4\) 的贡献是多少:
- 区间 \([1,8]\) 的 \(sum\) 里包含一个 \(4\);你一定会进入这个区间,贡献 \(4\times 1=4\)。
- 区间 \([1,4]\) 的 \(sum\) 里包含一个 \(4\);你有 \(50\%\) 概率进入这个区间,贡献 \(4\times 50\% =2\)。
- 区间 \([3,4]\) 的 \(sum\) 里包含一个 \(4\);你有 \(25\%\) 概率进入这个区间,贡献 \(4\times 25\%=1\)。
- 区间 \([4,4]\) 的 \(sum\) 里包含一个 \(4\);你有 \(12.5\%\) 概率进入这个区间,贡献 \(4\times 12.5\% =0.5\)。
总的贡献是 \(4+2+1+0.5=7.5\)。
我们从这个计算贡献的过程中清晰地发现,到达某个区间的概率是公比为 \(\dfrac{1}{2}\) 的等比数列——\(1,50\%,25\%,12.5\%\cdots\)。所以,只要我们知道了一个值为 \(a_i\) 的叶子结点的深度 \(dep_i\),就能通过等比数列求和计算出它的贡献:\(a_i\times\dfrac{1-(\frac{1}{2})^{dep_i}}{1-\frac{1}{2}}=a_i\times\dfrac{2^{dep_i}-1}{2^{dep_i-1}}\)。
对于不带修的情况,可以在建树的过程中记录 \(dep_i\),然后 \(O(n)\) 地求出总贡献。
下面研究区间加的情况:对于 \(l\le i\le r\),我们在每一个 \(a_i\) 上增加一个 \(x\)。我们发现这个 \(x\) 的贡献可以按照和 \(a_i\) 相同的逻辑计算贡献——\(x\times\dfrac{2^{dep_i}-1}{2^{dep_i-1}}\)。那么,答案的算法就显而易见了:
- 求出初始状态的总期望 \(ans\);
- 每次修改后,遍历 \(l\le i\le r\),将 \(x\) 带来的新贡献加入 \(ans\);
- 输出 \(ans\)。
第二步 \(O(mn)\) 的复杂度瓶颈,可以用前缀和优化,总复杂度 \(O(n\log n+m)\)。
对于分母的处理,方便计算起见,我们把所有的 \(\dfrac{1}{2^{dep_i}}\) 通分成 \(\dfrac{2^{maxDep-dep_i}}{2^{maxDep}}\),其中 \(maxDep=\max\limits_{i=1}^{n}{dep_i}\)。这样我们只需要计算分子,在输出答案时再乘上 \(\dfrac{qwq}{2^{maxDep}}\) 即可。为了避免溢出,提前对 \(2^{maxDep},qwq\) 除以最大公约数。
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1e6 + 10;
int maxDep, dep[N], a[N];
LL pre[N], sum[N << 2];
#define ls p << 1
#define rs p << 1 | 1
inline void refresh(int p) { sum[p] = sum[ls] + sum[rs]; }
void build(int p, int l, int r, int d) {
if (l == r) {
sum[p] = a[l];
dep[l] = d;
maxDep = max(d, maxDep);
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid, d + 1);
build(rs, mid + 1, r, d + 1);
refresh(p);
}
LL ask(int p, int l, int r, int s) {
s += sum[p];
if (l == r) {
return (1LL << (maxDep - dep[l])) * s;
}
int mid = (l + r) >> 1;
return ask(ls, l, mid, s) + ask(rs, mid + 1, r, s);
}
int main() {
// freopen("P3924_1.in", "r", stdin);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m, p;
cin >> n >> m >> p;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
build(1, 1, n, 0);
LL ans = ask(1, 1, n, 0);
LL den = 1LL << maxDep;
LL div = __gcd(den, (LL)p);
den /= div, p /= div;
for (int i = 1; i <= n; i++) {
pre[i] = pre[i - 1] + (((1 << dep[i] + 1) - 1) << (maxDep - dep[i]));
}
while (m--) {
int l, r, x;
cin >> l >> r >> x;
ans += (pre[r] - pre[l - 1]) * x;
cout << ans * p / den << '\n';
}
return 0;
}

浙公网安备 33010602011771号