【bzoj4785】[Zjoi2017]树状数组 线段树套线段树
题目描述
漆黑的晚上,九条可怜躺在床上辗转反侧。难以入眠的她想起了若干年前她的一次悲惨的OI 比赛经历。那是一道基础的树状数组题。给出一个长度为 n 的数组 A,初始值都为 0,接下来进行 m 次操作,操作有两种:
输入
输出
样例输入
5 5
1 3 3
2 3 5
2 4 5
1 1 3
2 2 5
样例输出
1
0
665496236
题解
线段树套线段树
“如果你对树状数组比较熟悉,不难发现”本题中树状数组求的是后缀和。
那么当$l-1\neq 0$时(等于0时再单独讨论),求出的结果即为$\sum\limits_{i=l-1}^{r-1}A_i$,若与$\sum\limits_{i=l}^rA_i$相等,则要求$A_{l-1}=A_r$。所以只需要求出$A_{l-1}=A_r$的概率即可。
我们想,对于修改操作[l,r],如果已经确定了左端点t和右端点k,如何更新t与k(k>t)相等的概率呢?
肯定是要分情况讨论,当然其中只有当$t$或$k\in[l,r]$时才会产生影响。
1.当$t\in[1,l-1]$,$k\in[l,r]$时,不影响的概率为1-p
2.当$t\in[l,r]$,$k\in[l,r]$时,不影响的概率为1-2p
3.当$t\in[l,r]$,$k\in[r+1,n]$时,不影响的概率为1-p。
如果确定了t,我们显然可以使用线段树维护这三段区间。至于概率的问题,如果原来相等的概率为p,不影响的概率为q,那么新的相等的概率显然为$p·q+(1-p)(1-q)$。并且这个式子满足交换律和结合律,因此更新顺序是不需要考虑的(并且可以标记永久化)。
而由于t的存在情况也是连续的区间,所以我们还需要一颗线段树维护左端点t,所以需要线段树套线段树,即二维线段树。
具体实现:使用类似于标记永久化的思想,选择一段外层区间和内层区间,就把(外层区间对应的外层节点)对应的(内层区间对应的内层节点)更新。
至于查询[l,r],则查找(外层线段树中l-1对应的节点)对应的(内层线段树中r对应的节点)。因为永久化了标记,所以所有经过的节点对答案的贡献都需要记录到答案中(特别是外层线段树)。
以上就是$l\neq 1$的情况,至于l=1的情况,同理,要保证的是r的前缀和等于后缀和,采用同样的思路维护一下就好了,具体见代码中对外层线段树0节点的操作。
代码真心不长~
#include <cstdio> #include <cstring> #include <algorithm> #define N 100010 using namespace std; typedef long long ll; const ll mod = 998244353; int root[N << 2] , ls[N << 8] , rs[N << 8] , tot , n; ll sum[N << 8]; ll cal(ll x , ll y) { return (x * y + (1 - x + mod) * (1 - y + mod)) % mod; } ll pow(ll x , ll y) { ll ans = 1; while(y) { if(y & 1) ans = ans * x % mod; x = x * x % mod , y >>= 1; } return ans; } void update(int b , int e , ll v , int l , int r , int &x) { if(!x) x = ++tot , sum[x] = 1; if(b <= l && r <= e) { sum[x] = cal(sum[x] , v); return; } int mid = (l + r) >> 1; if(b <= mid) update(b , e , v , l , mid , ls[x]); if(e > mid) update(b , e , v , mid + 1 , r , rs[x]); } ll query(int p , int l , int r , int x) { if(!x) return 1; if(l == r) return sum[x]; int mid = (l + r) >> 1; if(p <= mid) return cal(sum[x] , query(p , l , mid , ls[x])); else return cal(sum[x] , query(p , mid + 1 , r , rs[x])); } void modify(int p , int q , ll v , int b , int e , int l , int r , int x) { if(p <= l && r <= q) { update(b , e , v , 1 , n , root[x]); return; } int mid = (l + r) >> 1; if(p <= mid) modify(p , q , v , b , e , l , mid , x << 1); if(q > mid) modify(p , q , v , b , e , mid + 1 , r , x << 1 | 1); } ll solve(int p , int q , int l , int r , int x) { if(l == r) return query(q , 1 , n , root[x]); int mid = (l + r) >> 1; if(p <= mid) return cal(query(q , 1 , n , root[x]) , solve(p , q , l , mid , x << 1)); else return cal(query(q , 1 , n , root[x]) , solve(p , q , mid + 1 , r , x << 1 | 1)); } int main() { int m , opt , l , r; ll p; scanf("%d%d" , &n , &m); while(m -- ) { scanf("%d%d%d" , &opt , &l , &r); if(opt == 1) { p = pow(r - l + 1 , mod - 2); if(l > 1) modify(1 , l - 1 , (1 - p + mod) % mod , l , r , 0 , n , 1) , modify(0 , 0 , 0 , 1 , l - 1 , 0 , n , 1); if(r < n) modify(l , r , (1 - p + mod) % mod , r + 1 , n , 0 , n , 1) , modify(0 , 0 , 0 , r + 1 , n , 0 , n , 1); modify(l , r , (1 - (p << 1) % mod + mod) % mod , l , r , 0 , n , 1) , modify(0 , 0 , p , l , r , 0 , n , 1); } else printf("%lld\n" , solve(l - 1 , r , 0 , n , 1)); } return 0; }