[Luogu 4927] 梦美与线段树 题解

梦美与线段树

题意

给定一个长度为 n n n 的序列,对这个序列构造一棵维护区间和的线段树。

定义线段树上一个节点的权值 v a l x val_x valx,为其所对应区间的权值和。

梦美从线段树的根节点开始访问,每次有 v a l l c ( x ) / v a l x val_{lc(x)}/val_x vallc(x)/valx 的概率进入左子树, v a l r c ( x ) / v a l x val_{rc(x)}/val_x valrc(x)/valx 的概率进入右子树。(根据定义显然 v a l x = v a l l c ( x ) + v a l r c ( x ) val_x = val_{lc(x)} + val_{rc(x)} valx=vallc(x)+valrc(x)。)当到达叶子节点时,访问结束。

记一次访问的收获为访问途径所有节点的权值之和。

每次查询,求梦美在当前线段树进行一次访问的收获的数学期望。

有若干次修改操作,每次修改使一个区间 [ l , r ] [l, r] [l,r] 加上 v v v,不考虑 lazy_tag。可将区间加视为 r − l + 1 r-l+1 rl+1 次单点加。

答案对 998244353 998244353 998244353 取模, 1 ≤ n , q ≤ 1 0 5 1 \le n, q \le 10^5 1n,q105

思路

注意到遍历到 x x x 的概率为 v a l x / ( ∑ i = 1 n a i ) val_x / (\sum_{i=1}^n a_i) valx/(i=1nai) x x x 对答案的贡献为 v a l x 2 / ( ∑ i = 1 n a i ) val_x^2 / (\sum_{i=1}^n a_i) valx2/(i=1nai)

考虑先算分子,最后再除。(这里因为不保证约分前分母取模非 0 0 0,所以先用 __int128 算出分子分母,然后除以 gcd ⁡ \gcd gcd。)

显然线段树还是要建的。

v a l val val 为正常的区间权值和(正常维护即可), l e n len len 为区间长度。设:

a n s x = ∑ i ∈ t r e e ( x ) v a l i 2 t m p x = ∑ i ∈ t r e e ( x ) v a l i ⋅ l e n i s u m x = ∑ i ∈ t r e e ( x ) l e n i 2 ans_x = \sum_{i\in tree(x)}val_i^2 \\tmp_x = \sum_{i\in tree(x)} val_i \cdot len_i \\sum_x = \sum_{i\in tree(x)} len_i^2 ansx=itree(x)vali2tmpx=itree(x)valilenisumx=itree(x)leni2

当前区间加 v v v 时:

a n s x ′ = ∑ i ∈ t r e e ( x ) ( v a l i + v ⋅ l e n i ) 2 = ∑ i ∈ t r e e ( x ) ( v a l i 2 + 2 v ⋅ v a l i ⋅ l e n i + v 2 ⋅ l e n i 2 ) 2 = ∑ i ∈ t r e e ( x ) v a l i 2 + 2 v ∑ i ∈ t r e e ( x ) v a l i ⋅ l e n i + v 2 ∑ i ∈ t r e e ( x ) l e n i 2 = a n s x + 2 v ⋅ t m p x + v 2 ⋅ s u m x \begin{aligned} ans_x'=& \sum_{i\in tree(x)}(val_i + v\cdot len_i) ^2\\ =&\sum_{i\in tree(x)}(val_i^2+2v\cdot val_i\cdot len_i + v^2 \cdot len_i^2)^2\\ =&\sum_{i\in tree(x)}val_i^2 + 2v\sum_{i\in tree(x)}val_i \cdot len_i + v^2 \sum_{i\in tree(x)} len_i^2\\ =&ans_x + 2v\cdot tmp_x + v^2 \cdot sum_x \end{aligned} ansx====itree(x)(vali+vleni)2itree(x)(vali2+2vvalileni+v2leni2)2itree(x)vali2+2vitree(x)valileni+v2itree(x)leni2ansx+2vtmpx+v2sumx

t m p x ′ = ∑ i ∈ t r e e ( x ) ( v a l i + v ⋅ l e n i ) ⋅ l e n i = ∑ i ∈ t r e e ( x ) v a l i ⋅ l e n i + v ∑ i ∈ t r e e ( x ) l e n i 2 = t m p x + v ⋅ s u m x \begin{aligned} tmp_x' =& \sum_{i\in tree(x)} (val_i+v \cdot len_i) \cdot len_i\\ =&\sum_{i\in tree(x)} val_i \cdot len_i +v \sum_{i\in tree(x)} len_i^2\\ =&tmp_x + v \cdot sum_x \end{aligned} tmpx===itree(x)(vali+vleni)leniitree(x)valileni+vitree(x)leni2tmpx+vsumx

每次查询分子就是 a n s r o o t ans_{root} ansroot,分母就是 v a l r o o t val_{root} valroot

代码

线段树套路题,关键是把需要维护什么信息,以及结构体里面的 modify() 函数和 operator+ 的操作搞明白。别的就是感觉已经打了不下 100 遍的板子。

我目前线段树的码风已经差不多定型了,就这样将就着看吧。

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#include <utility>
#define fi first
#define se second

using namespace std;
using LL = long long;
using LLL = __int128;
const int MAXN = 1e5+5;
const int mod = 998244353;

int read() {
    char ch;
    int res = 0, op = 0;
    do ch = getchar(), op |= ch == '-'; while (ch < '0' || ch > '9');
    do res = (res<<3)+(res<<1)+ch-48, ch = getchar(); while (ch>='0' && ch<='9');
    return op ? -res : res;
}

int n, m;
int arr[MAXN];

namespace SGT {
    struct Node {
        LLL ans, tmp, val, len, sum, tag;
        Node(){}
        Node(LLL v) {
            ans = v * v, tmp = val = v, len = sum = 1, tag = 0;
        }
        void modify(LLL x) {
            ans += 2 * x * tmp + x * x * sum;
            tmp += x * sum;
            val += x * len;
            tag += x;
        }
    } s[MAXN<<2];

    Node operator+(Node lc, Node rc) {
        Node x;
        x.val = lc.val + rc.val;
        x.len = lc.len + rc.len;
        x.ans = lc.ans + rc.ans + x.val * x.val;
        x.tmp = lc.tmp + rc.tmp + x.val * x.len;
        x.sum = lc.sum + rc.sum + x.len * x.len;
        x.tag = 0;
        return x;
    }

    void build(int cur, int l, int r) {
        if (l == r) return s[cur] = Node(arr[l]), void();
        int mid = (l + r) >> 1;
        build(cur<<1, l, mid), build(cur<<1|1, mid+1, r);
        s[cur] = s[cur<<1] + s[cur<<1|1];
    }

    void spread(int cur, int l, int r) {
        if (!s[cur].tag) return;
        int mid = (l + r) >> 1;
        s[cur<<1].modify(s[cur].tag);
        s[cur<<1|1].modify(s[cur].tag);
        s[cur].tag = 0;
    }

    void update(int cur, int l, int r, int L, int R, LLL x) {
        if (L <= l && R >= r) return s[cur].modify(x);
        spread(cur, l, r);
        int mid = (l + r) >> 1;
        if (L <= mid) update(cur<<1, l, mid, L, R, x);
        if (R > mid) update(cur<<1|1, mid+1, r, L, R, x);
        s[cur] = s[cur<<1] + s[cur<<1|1];
    }
}

int qpow(int x, int y) {
    int r = 1;
    for (; y; y >>= 1) {
        if (y & 1) r = 1ll * r * x % mod;
        x = 1ll * x * x % mod;
    }
    return r;
}

int main() {
    #ifndef ONLINE_JUDGE
    freopen("lg4927.in", "r", stdin);
    freopen("lg4927.out", "w", stdout);
    #endif
    n = read(), m = read();
    for (int i = 1; i <= n; ++i) arr[i] = read();
    SGT::build(1, 1, n);
    while (m--) {
        int op = read();
        if (op == 1) {
            int l = read(), r = read(), v = read();
            SGT::update(1, 1, n, l, r, v);
        } else {
            LLL fz = SGT::s[1].ans, fm = SGT::s[1].val;
            LLL d = __gcd(fz, fm);
            fz /= d, fm /= d;
            printf("%d\n", (int)(fz % mod * qpow(fm%mod, mod-2) % mod));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值