P3924 康娜的线段树

首先考虑每一个 ai(叶子结点)对于总期望值有多少贡献。不妨以区间 [1,8] 建线段树,看看 ai=4 的贡献是多少:

  • 区间 [1,8]sum 里包含一个 4;你一定会进入这个区间,贡献 4×1=4
  • 区间 [1,4]sum 里包含一个 4;你有 50% 概率进入这个区间,贡献 4×50%=2
  • 区间 [3,4]sum 里包含一个 4;你有 25% 概率进入这个区间,贡献 4×25%=1
  • 区间 [4,4]sum 里包含一个 4;你有 12.5% 概率进入这个区间,贡献 4×12.5%=0.5

总的贡献是 4+2+1+0.5=7.5

我们从这个计算贡献的过程中清晰地发现,到达某个区间的概率是公比为 12 的等比数列——1,50%,25%,12.5%。所以,只要我们知道了一个值为 ai 的叶子结点的深度 depi,就能通过等比数列求和计算出它的贡献:ai×1(12)depi112=ai×2depi12depi1

对于不带修的情况,可以在建树的过程中记录 depi,然后 O(n) 地求出总贡献。

下面研究区间加的情况:对于 lir,我们在每一个 ai 上增加一个 x。我们发现这个 x 的贡献可以按照和 ai 相同的逻辑计算贡献——x×2depi12depi1。那么,答案的算法就显而易见了:

  • 求出初始状态的总期望 ans
  • 每次修改后,遍历 lir,将 x 带来的新贡献加入 ans
  • 输出 ans

第二步 O(mn) 的复杂度瓶颈,可以用前缀和优化,总复杂度 O(nlogn+m)

对于分母的处理,方便计算起见,我们把所有的 12depi 通分成 2maxDepdepi2maxDep,其中 maxDep=maxi=1ndepi。这样我们只需要计算分子,在输出答案时再乘上 qwq2maxDep 即可。为了避免溢出,提前对 2maxDep,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 @   XYukari  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示