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;
}
posted @ 2024-03-25 18:57  XYukari  阅读(32)  评论(0)    收藏  举报