ZJOI2017 树状数组

【题解】[ZJOI2017] 树状数组

题目描述:

漆黑的晚上,九条可怜躺在床上辗转反侧。难以入眠的她想起了若干年前她的一次悲惨的 OI 比赛经历。那是一道基础的树状数组题。

给出一个长度为 nn 的数组 AA,初始值都为 00,接下来进行 mm 次操作,操作有两种:

  • 1 x1\ x,表示将 AxA_x 变成 (Ax+1)mod2(A_x + 1) \bmod 2
  • 2 l r2\ l\ r,表示询问 (i=lrAi)mod2(\sum_{i=l}^r A_i) \bmod 2

尽管那个时候的可怜非常的 simple,但是她还是发现这题可以用树状数组做。当时非常 young 的她写了如下的算法:

其中 lowbit(x)\mathrm{lowbit}(x) 表示数字 xx 最低的非 00 二进制位,例如 lowbit(5)=1,lowbit(12)=4\text{lowbit}(5) = 1, \text{lowbit}(12) = 4。进行第一类操作的时候就调用 Add(x)\mathrm{Add}(x),第二类操作的时候答案就是 Query(l,r)\mathrm{Query}(l, r)

如果你对树状数组比较熟悉,不难发现可怜把树状数组写错了:Add\text{Add}Find\text{Find}xx 变化的方向反了。因此这个程序在最终测试时华丽的爆 0 了。

然而奇怪的是,在当时,这个程序通过了出题人给出的大样例——这也是可怜没有进行对拍的原因。

现在,可怜想要算一下,这个程序回答对每一个询问的概率是多少,这样她就可以再次的感受到自己是一个多么非的人了。然而时间已经过去了很多年,即使是可怜也没有办法完全回忆起当时的大样例。幸运的是,她回忆起了大部分内容,唯一遗忘的是每一次第一类操作的 xx 的值,因此她假定这次操作的 xx 是在 [li,ri][l_i, r_i] 范围内等概率随机的。

具体来说,可怜给出了一个长度为 nn 的数组 AA,初始为 00,接下来进行了 mm 次操作:

  • 1 l r1\ l\ r,表示在区间 [l,r][l, r] 中等概率选取一个 xx 并执行 Add(x)\text{Add}(x)

  • 2 l r2\ l\ r,表示询问执行 Query(l,r)\text{Query}(l, r) 得到的结果是正确的概率是多少。

数据范围

测试点编号 nn mm 其他约定
1010 10510^5 10510^5

对于 100%100\% 的数据,保证 1lrn1\leq l\leq r\leq n

题目分析:

事实证明不逼自己一把永远没有进步,我竟然独立地做出来了这道题,包括代码。

树状数组倒着做应该就是我们在学习树状数组的时候会接触到的奇技淫巧,倒着做的话每次查询就相当于后缀查询。
这样代码里查询答案时的 al1ara_{l-1} - a_r 其实就是在 [l,r][l,r] 的答案的基础上新增加了 al1a_{l-1} 并且多删除了 ara_r,所以要使得其答案正确就是 al1a_{l-1}ara_r 的贡献可以相互抵消。
也就是 al1=ara_{l-1} = a_r
这里如果对于每一个数单独维护的话会出现相互影响就很不好做,一个经典的 trick 就是维护 al1ar=0a_{l-1} - a_r = 0,也就是维护它们的差值。
暴力的想法就是设 dpidp_i 表示 al1ara_{l-1} - a_r 的差值为 ii 的概率,因为我们都是模 22 意义下的,所以差值只可能为 00 或者 11 然后从前到后扫每一个 11 操作更新 dpdp 值即可,这样的复杂度就是 O(q2)O(q^2)
考虑优化,也就是这个 dpdp 可以写成矩阵乘法的形式,所以我们只需要能快速维护对应的转移矩阵的乘积即可。
可以分类讨论:
对于 l,rl,r,若一个区间只覆盖了其中一个点则会让差改变的概率为 1rl+1\frac{1}{r-l+1},若一个区间同时覆盖了这两个点则会让差改变的概率为 2rl+1\frac{2}{r-l+1}
所以就可以对于这个写出对应的矩阵,然后使用二维线段树快速维护矩阵乘法即可。
一个细节就是给定的代码里特判了若 xx00 则询问的值为 00,这个时候就是相当于 rr 这个前缀与 rr 这个后缀的值相同的时候答案才正确,所以可以再进行包含不包含 rr 的分类讨论然后得到矩阵即可。
但是直接维护矩阵实在是太慢了,可以考虑维护差值变/不变的概率,然后类似矩阵乘法地形式合并即可。
因为变的概率+不变的概率为 11,所以只需要维护变的概率即可。

代码:

点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include<bits/stdc++.h>
#define PII pair<int,int>
using namespace std;
const int N = 1e5 + 500;
const int MOD = 998244353;
int val[200 * N][5];
int tot,inv[10 * N],n,m,ls[200 * N],rs[200 * N],rt[10 * N];
int times(int a,int b){
int c;
c = (1ll * a * b % MOD + 1ll * (1-a)%MOD * (1-b) % MOD)%MOD;
c = (c + MOD)%MOD;
return c;
}
int power(int a,int b){
int res = 1;
while(b){
if(b & 1) res = 1ll * res * a % MOD;
a = 1ll * a * a % MOD;
b >>= 1;
}
return res;
}
void modify_y(int &now,int now_l,int now_r,int pos,int len){
if(!now){
now = ++tot;
for(int i=0; i<=3; i++) val[now][i] = 1;
}
if(now_l == now_r){
int tmp;
// 1 / len 的概率变
tmp = (1 - inv[len] + MOD)%MOD;
val[now][0] = times(val[now][0],tmp);

// 2 / len 的概率变
if(len >= 2){
tmp = (1 - 1ll * 2 * inv[len]%MOD + MOD) % MOD;
val[now][1] = times(val[now][1],tmp);
}

// 1 / len 的概率不变
tmp = inv[len];
val[now][2] = times(val[now][2],tmp);

// 一定变
tmp = 0;
val[now][3] = times(val[now][3],tmp);
return;
}
int mid = (now_l + now_r)>>1;
if(pos <= mid) modify_y(ls[now],now_l,mid,pos,len);
if(pos > mid) modify_y(rs[now],mid+1,now_r,pos,len);
for(int i=0; i<=3; i++){
val[now][i] = 1;
if(ls[now]) val[now][i] = times(val[now][i],val[ls[now]][i]);
if(rs[now]) val[now][i] = times(val[now][i],val[rs[now]][i]);
}
}
void modify_x(int now,int now_l,int now_r,int x,int y,int len){
modify_y(rt[now],1,n,y,len);
if(now_l == now_r) return;
int mid = (now_l + now_r)>>1;
if(x <= mid) modify_x(now<<1,now_l,mid,x,y,len);
if(x > mid) modify_x(now<<1|1,mid+1,now_r,x,y,len);
}
int query_y(int now,int now_l,int now_r,int l,int r,int opt){
if(!now) return 1;
if(l <= now_l && now_r <= r) return val[now][opt];
int mid = (now_l + now_r)>>1;
int ans = 1;
if(l <= mid) ans = times(ans,query_y(ls[now],now_l,mid,l,r,opt));
if(r > mid) ans = times(ans,query_y(rs[now],mid+1,now_r,l,r,opt));
return ans;
}
int query_x(int now,int now_l,int now_r,int xl,int xr,int yl,int yr,int opt){
if(xl > xr || yl > yr) return 1;
if(xl <= now_l && now_r <= xr) return query_y(rt[now],1,n,yl,yr,opt);
int mid = (now_l + now_r)>>1;
int ans = 1;
if(xl <= mid) ans = times(ans,query_x(now<<1,now_l,mid,xl,xr,yl,yr,opt));
if(xr > mid) ans = times(ans,query_x(now<<1|1,mid+1,now_r,xl,xr,yl,yr,opt));
return ans;
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d%d",&n,&m);
inv[0] = 1;
for(int i=1; i<=n; i++) inv[i] = power(i,MOD-2);
while(m--){
int opt,l,r;scanf("%d%d%d",&opt,&l,&r);
if(opt == 1) modify_x(1,1,n,l,r,r-l+1);
if(opt == 2){
l = l-1;
int ans;
if(l == 0) ans = times(times(query_x(1,1,n,1,r-1,1,r-1,3),query_x(1,1,n,r+1,n,r+1,n,3)),query_x(1,1,n,1,r,r,n,2));
else ans = times(times(query_x(1,1,n,1,l,l,r-1,0),query_x(1,1,n,l+1,r,r,n,0)),query_x(1,1,n,1,l,r,n,1));
printf("%d\n",ans);
}
}
return 0;
}
作者

linyihdfj

发布于

2023-09-19

更新于

2023-09-19

许可协议