题目描述

漆黑的晚上,九条可怜躺在床上辗转反侧。难以入眠的她想起了若干年前她的一次悲惨的OI 比赛经历。那是一道基础的树状数组题。给出一个长度为 n 的数组 A,初始值都为 0,接下来进行 m 次操作,操作有两种:

1 x,表示将 Ax 变成 (Ax + 1) mod 2。
2 l r,表示询问 sigma(Ai) mod 2,L<=i<=r
尽管那个时候的可怜非常的 simple,但是她还是发现这题可以用树状数组做。当时非常young 的她写了如下的算法:
1: function Add(x)
2: while x > 0 do
3: A
x ← (Ax + 1) mod 2
4: x ← x - lowbit(x)
5: end while
6: end function
7:
8: function Find(x)
9: if x == 0 then
10: return 0
11: end if
12: ans ← 0
13: while x ≤ n do
14: ans ← (ans + Ax) mod 2
15: x ← x + lowbit(x)
16: end while
17: return ans
18: end function
19:
20: function Query(l, r)
21: ansl ← Find(l - 1)
22: ansr ← Find(r)
23: return (ansr - ansl + 2) mod 2
24: end function
其中 lowbit(x) 表示数字 x 最高的非 0 二进制位,例如 lowbit(5) = 1, lowbit(12) = 4。进行第一类操作的时候就调用 Add(x),第二类操作的时候答案就是 Query(l, r)。如果你对树状数组比较熟悉,不难发现可怜把树状数组写错了: Add和Find 中 x 变化的方向反了。因此这个程序在最终测试时华丽的爆 0 了。然而奇怪的是,在当时,这个程序通过了出题人给出的大样例——这也是可怜没有进行对拍的原因。现在,可怜想要算一下,这个程序回答对每一个询问的概率是多少,这样她就可以再次的感受到自己是一个多么非的人了。然而时间已经过去了很多年,即使是可怜也没有办法完全回忆起当时的大样例。幸运的是,她回忆起了大部分内容,唯一遗忘的是每一次第一类操作的 x的值,因此她假定这次操作的 x 是在 [li, ri] 范围内 等概率随机 的。具体来说,可怜给出了一个长度为 n 的数组 A,初始为 0,接下来进行了 m 次操作:
1 l r,表示在区间 [l, r] 中等概率选取一个 x 并执行 Add(x)。
2 l r,表示询问执行 Query(l, r) 得到的结果是正确的概率是多少。

输入

第一行输入两个整数 n, m。
接下来 m 行每行描述一个操作,格式如题目中所示。
N<=10^5,m<=10^5,1<=L<=R<=N

输出

对于每组询问,输出一个整数表示答案。如果答案化为最简分数后形如 x/y,那么你只需要输出 x*y^?1 mod 998244353 后的值。(即输出答案模 998244353)。

样例输入

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;
}

最新文章

  1. Java for LeetCode 151 Reverse Words in a String
  2. n阶乘 尾数0的个数
  3. win7 Android环境搭配
  4. 20141104--SQL连接查询,联合查询
  5. ASIHTTPREQUEST 文档
  6. Win7设置共享文件夹
  7. Bleed Brake Master Cylinder with Intelligent Tester IT2
  8. WebService 简单安全验证
  9. BinTools 十六进制转换
  10. POJ 3458 Colour Sequence
  11. Jmeter遇到的坑
  12. mongoDB之数据类型
  13. Java中的换行符
  14. ESXI常用命令
  15. JIRA应用的内存参数设置不当+容器没有对资源进行限制导致服务挂掉的例子
  16. RESTful Service API 常见问题解决方案
  17. js 高效拼接字符串
  18. Opengl绘制我们的小屋(一)球体,立方体绘制
  19. IntelliJ IDEA 2017版 编译器使用学习笔记(三) (图文详尽版);IDE快捷键使用
  20. Android Studio连接真机

热门文章

  1. unity热更新方案对比
  2. 【转】iOS学习笔记(十五)——数据库操作(SQLite)
  3. 第010课_掌握ARM芯片时钟体系
  4. HTML5中的webSocket、ajax、http
  5. sessionStorage对象
  6. Codeforces 517 #A
  7. 对O(logN)复杂度的推导
  8. HDU-1072-Nightmares
  9. GoogleTest 之路1-Generic Build Instructions编译指导总方案
  10. ubuntu 16.04下如何打造 sublime python编程环境