[BZOJ4785][ZJOI2017]树状数组(树套树)

树状数组与线段树概率更新
本文探讨了一种利用树状数组和线段树解决区间修改与单点查询问题的方法,具体针对如何计算两个指定位置元素相等的概率,并讨论了四种不同情况下的概率更新策略。

可以发现,这个树状数组实际上求的是后缀和。而这样子求出的区间和,只有 l1 r 两个点的差别。于是第二问就转化成A[l1]=A[r]的概率,但是要特判 l=1 的情况( l=1 时正确的概率为 ri=1A[i]ni=rA[i](mod2) 的概率)。
这里设 f(i,j) 表示 A[i]=A[j] 的概率, f(0,j) 表示 ji=1A[i]ni=jA[i](mod2) 的概率。
Q:为什么要维护任意两个点相等的概率,而不是维护一个点为 1 的概率p(i),询问结果为 p(l1)p(r)+(1p(l1))(1p(r)) 呢?
A:这里每个点为 1 的概率不是独立的,所以结果不一定是p(l1)p(r)+(1p(l1))(1p(r))。举个例子,如果执行了1 1 2,那么 A[1] A[2] 1 的概率都是12,但 A[1]=A[2] 的概率是 0 而不是12。原因很简单:因为对于每个 1 操作,在[l,r]内只能有一个值被修改,所以在上面的例子里,如果 A[1]=1 ,那么 A[2] 必须为 0 ,反过来也一样。所以这时候A[1]不可能等于 A[2]
回到问题,考虑一个修改操作对 f 的影响。分4种情况考虑:
1、 i,j 两个端点,一个在 [l,r] 内,一个在 [l,r] 外。那么这时候的 A[i]=A[j] 的真假就有 1rl+1 的概率被反转。即 i[1,l1],j[l,r]i[l,r],j[r+1,n] f(i,j)=f(i,j)rlrl+1+(1f(i,j))1rl+1
2、 i,j 两个端点都在 [l,r] 内。则此时 A[i]=A[j] 的真假被反转的条件是 i 被反转或j被反转,即有 2rl+1 的概率被反转。也就是说, i[l,r],j[l,r] f(i,j)=f(i,j)rl1rl+1+(1f(i,j))2rl+1
3、 i=0 j [l,r]外。那么被反转的这个点,要么在 j 的前面(j>r)要么在 j 的后面(j<l),也就是说 jk=1A[k]nk=jA[k](mod2) 的真假一定被反转。即 j[l,r] f(0,j)=1f(0,j)
4、 i=0 j [l,r]内。那么可以看出, jk=1A[k]nk=jA[k](mod2) 的真假不被反转的充分必要条件是被反转的点恰好为 j ,即jk=1A[k]nk=jA[k](mod2)的值有 rlrl+1 的概率被反转。也就是说 j[l,r] f(0,j)=f(0,j)1rl+1+(1f(0,j))rlrl+1
而询问就是询问 f(l1,r) 的值。
可以用线段树套线段树维护 f 的值,支持二维区间修改和单点询问。实现见代码。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define p2 p << 1
#define p3 p << 1 | 1
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 4e5 + 193, W = 4e7 + 97, PYZ = 998244353;
int n, m, rt[N], QAQ;
struct cyx {
    int lc, rc, val;
    void init() {lc = rc = 0; val = 1;}
} T[W];
int qpow(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = 1ll * res * a % PYZ;
        a = 1ll * a * a % PYZ;
        b >>= 1;
    }
    return res;
}
int calc(int x, int y) {
    int res = 1ll * x * y % PYZ, u = 1 - x, v = 1 - y;
    if (u < 0) u += PYZ; if (v < 0) v += PYZ;
    return (res + 1ll * u * v % PYZ) % PYZ;
}
void modify(int l, int r, int s, int e, int v, int &p) {
    if (!p) T[p = ++QAQ].init();
    if (l == s && r == e) return (void) (T[p].val = calc(T[p].val, v));
    int mid = l + r >> 1;
    if (e <= mid) modify(l, mid, s, e, v, T[p].lc);
    else if (s >= mid + 1) modify(mid + 1, r, s, e, v, T[p].rc);
    else modify(l, mid, s, mid, v, T[p].lc),
        modify(mid + 1, r, mid + 1, e, v, T[p].rc);
}
void change(int l, int r, int s, int e, int st, int ed, int v, int p) {
    if (l == s && r == e) return modify(1, n, st, ed, v, rt[p]);
    int mid = l + r >> 1;
    if (e <= mid) change(l, mid, s, e, st, ed, v, p2);
    else if (s >= mid + 1) change(mid + 1, r, s, e, st, ed, v, p3);
    else change(l, mid, s, mid, st, ed, v, p2),
        change(mid + 1, r, mid + 1, e, st, ed, v, p3);
}
int query(int l, int r, int x, int p) {
    if (!p) return 1; int res = T[p].val;
    if (l == r) return res; int mid = l + r >> 1;
    if (x <= mid) res = calc(res, query(l, mid, x, T[p].lc));
    else res = calc(res, query(mid + 1, r, x, T[p].rc));
    return res;
}
int ask(int l, int r, int x, int y, int p) {
    int res = query(1, n, y, rt[p]);
    if (l == r) return res; int mid = l + r >> 1;
    if (x <= mid) res = calc(res, ask(l, mid, x, y, p2));
    else res = calc(res, ask(mid + 1, r, x, y, p3));
    return res;
}
int main() {
    int i, op, x, y; n = read(); m = read();
    while (m--) {
        op = read(); x = read(); y = read();
        if (op == 1) {
            int v = qpow(y - x + 1, PYZ - 2), w, u = (1 - v + PYZ) % PYZ;
            if (x > 1) change(0, n, 1, x - 1, x, y, u, 1);
            if (y < n) change(0, n, x, y, y + 1, n, u, 1);
            w = (1 - (v << 1) % PYZ + PYZ) % PYZ;
            change(0, n, x, y, x, y, w, 1);
            if (x > 1) change(0, n, 0, 0, 1, x - 1, 0, 1);
            if (y < n) change(0, n, 0, 0, y + 1, n, 0, 1);
            change(0, n, 0, 0, x, y, v, 1);
        }
        else printf("%d\n", ask(0, n, x - 1, y, 1));
    }
    return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值