题目描述

漆黑的晚上,九条可怜躺在床上辗转反侧。难以入眠的她想起了若干年前她的一次悲惨的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;
}

【bzoj4785】[Zjoi2017]树状数组 线段树套线段树的更多相关文章

  1. 「ZJOI2017」树状数组(二维线段树)

    「ZJOI2017」树状数组(二维线段树) 吉老师的题目真是难想... 代码中求的是 \(\sum_{i=l-1}^{r-1}a_i\),而实际求的是 \(\sum_{i=l}^{r}a_i\),所以 ...

  2. [BZOJ4785][ZJOI2017]树状数组(概率+二维线段树)

    4785: [Zjoi2017]树状数组 Time Limit: 40 Sec  Memory Limit: 512 MBSubmit: 297  Solved: 195[Submit][Status ...

  3. BZOJ4785 [Zjoi2017]树状数组 【二维线段树 + 标记永久化】

    题目链接 BZOJ4785 题解 肝了一个下午QAQ没写过二维线段树还是很难受 首先题目中的树状数组实际维护的是后缀和,这一点凭分析或经验或手模观察可以得出 在\(\mod 2\)意义下,我们实际求出 ...

  4. P3688 [ZJOI2017] 树状数组 【二维线段树】

    题目描述:这里有一个写挂的树状数组: 有两种共\(m\)个操作: 输入\(l,r\),在\([l,r]\)中随机选择一个整数\(x\)执行\(\text{Add}(x)\) 输入\(l,r\),询问执 ...

  5. 洛谷 P3688 - [ZJOI2017]树状数组(二维线段树+标记永久化)

    题面传送门 首先学过树状数组的应该都知道,将树状数组方向写反等价于前缀和 \(\to\) 后缀和,因此题目中伪代码的区间求和实质上是 \(sum[l-1...n]-sum[r...n]=sum[l-1 ...

  6. LightOJ 1085(树状数组+离散化+DP,线段树)

    All Possible Increasing Subsequences Time Limit:3000MS     Memory Limit:65536KB     64bit IO Format: ...

  7. 敌兵布阵 HDU - 1166 (树状数组模板题,线段树模板题)

    思路:就是树状数组的模板题,利用的就是单点更新和区间求和是树状数组的强项时间复杂度为m*log(n) 没想到自己以前把这道题当线段树的单点更新刷了. 树状数组: #include<iostrea ...

  8. 牛客网 暑期ACM多校训练营(第一场)J.Different Integers-区间两侧不同数字的个数-离线树状数组 or 可持久化线段树(主席树)

    J.Different Integers 题意就是给你l,r,问你在区间两侧的[1,l]和[r,n]中,不同数的个数. 两种思路: 1.将数组长度扩大两倍,for(int i=n+1;i<=2* ...

  9. day 1 堆 hash 线段树 树状数组 冰茶姬 字典树 二叉查找树

    来郑州的第二天,早上开始也没说什么就说了些注意安全,各种各样的注意安全... 冰茶姬: 原来再打食物链时看了一下冰茶姬,只注意了路径压缩,没想到还有什么按秩排序但确实快了不少... int find( ...

  10. HDU 1934 树状数组 也可以用线段树

    http://acm.hdu.edu.cn/showproblem.php?pid=1394 或者是我自己挂的专题http://acm.hust.edu.cn/vjudge/contest/view. ...

随机推荐

  1. 【UML】部署图Deployment diagram(实现图)(转)

    http://blog.csdn.net/sds15732622190/article/details/49049665 前言 下面要介绍UML中的部署图,和构件图一样,它也属于实现图的一种,五种静态 ...

  2. HDU - 5493 Queue 2015 ACM/ICPC Asia Regional Hefei Online(线段树)

    按身高排序,每个人前面最高的人数有上限,如果超出上限说明impossible, 每次考虑最小的人,把他放在在当前的从左往右第k+1个空位 因为要求字典序最小,所以每次k和(上限-k)取min值. 没有 ...

  3. python 基础之while无限循环

    用户登录程序 username = "chenxi" passwed = "testki" counter = 0 while counter < 3: ...

  4. CPP-基础:c++读取ini文件

    配置文件格式是[JP]K=2EC156673E 2F4240 5595F6 char str[50];GetPrivateProfileString("JP", "K&q ...

  5. centos Chrony设置服务器集群同步时间

    Chrony是一个开源的自由软件,像CentOS 7或基于RHEL 7操作系统,已经是默认服务,默认配置文件在 /etc/chrony.conf 它能保持系统时间与时间服务器(NTP)同步,让时间始终 ...

  6. C11 C语言文件的读写

    目录 文件的打开和关闭 字符流读写文件 文件的打开和关闭 fopen( ) fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE ...

  7. 万恶之源 Python

    学IT真他妈难受 从早上起来坐到晚上 一天对着电脑啪啪啪

  8. LLDB详解

    LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能.平时用Xcode运行程序,实际走的都是LLDB.熟练使用LLDB,可以让你debug事半功倍 L ...

  9. 洛谷 P1835 素数密度

    https://www.luogu.org/problemnew/show/P1835 对于40%,对每个数进行最大$O(\sqrt n)$的判断,因为n比较大所以超时. 想到线性筛,然而我们并不能筛 ...

  10. Protobuf有没有比JSON快5倍?用代码来击破pb性能神话

    转 http://www.sohu.com/a/136487507_505779 2017-04-26 07:58 程序设计 /58 /技术 导读:Google 的 Protocol Buffers ...