[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 <script type="math/tex" id="MathJax-Element-134">f</script>的值,支持二维区间修改和单点询问。实现见代码。
代码:

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值