【BZOJ4785】[Zjoi2017]树状数组

Description

漆黑的晚上,九条可怜躺在床上辗转反侧。难以入眠的她想起了若干年前她的一次悲惨的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) 得到的结果是正确的概率是多少。

Input

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

Output

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

Sample Input

5 5
1 3 3
2 3 5
2 4 5
1 1 3
2 2 5

Sample Output

1
0
665496236
//在进行完 Add(3) 之后, A 数组变成了 [0, 1, 1, 0, 0]。所以前两次询问可怜的程序答案都是1,因此第一次询问可怜一定正确,第二次询问可怜一定错误。

题解:发现这里给的树状数组的方向正好是反过来的,也就是说这里的树状数组维护的实际上是后缀xor和。那么后缀xor和与前缀xor和相等的情况就是:

[1,l-1]^[1,r]=[l-1,n]^[r,n]  --> [l,r]=[l-1,r-1] ---> [l-1]=[r]

也就是说我们求的是l的值和r的值相等的概率。然后到这里,大部分题解都说“这变成了一个二维数点问题”,然而本蒟蒻一脸mengbi,所以,这里还是换一种方法讲吧。

我们用(a,b)表示a的值和b的值相等的概率。加入我们想要修改[l,r]中随机一个点,那么我们先考虑所有l<=a<b<=r的点对。

对于点对a,b,一次修改中它们最多只有一个数改变,我们设$q=1-{2\over r-l+1}$,表示a,b相等性不变的概率,设p表示原来a,b相等的概率,那么$p=pq+(1-p)(1-q)$。并且,我们要对[l,r]中所有的点对都进行这个计算,那么我们可以认为(a,b)是二维平面上的一个点,我们要修改的是(l,l)-(r,r)这个矩形,这可以用二维线段树维护。

//问题:对于某个树上的节点x,我们先给它打了个标记q1,有想给它打个标记q2,这两个标记该如何处理呢?自己推一推就知道,因为一开始的p都是1,那么先处理q1和先处理q2的结果是相同的(也就是说标记满足交换律),设p打了q1标记变成p',我们在同样的给p'打个q2标记就行了。

再考虑a<l<=b<=r的点对(l<=a<=r<b的类似),这样的点对的相等性不变的概率就是$q=1-{1\over r-l+1}$。此时我们要修改的矩形就变成了(1,l-1)-(l,r),依旧二维线段树。

突然发现一种情况,当l=1时怎么办?因为l-1=0,所以此时要求的就是后缀xor和与前缀xor和相等的概率,单独维护一下就好了。

=======下面是二维线段树部分=======

本题要支持什么操作呢?矩形区间计算。因为二维线段树必须标记永久化,所以我们在第一位线段树时,每访问到一个合法的整区间,就进入第二维线段树去进行区间修改。这样,在查询的时候,我们的答案要将第一维线段树上 从根到叶子的所有节点的查询结果 全都算到一起,也就是说没经过一个节点就要更新答案。

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #define z(_) (((_)%mod+mod)%mod)
  5. #define lson x<<1
  6. #define rson x<<1|1
  7. using namespace std;
  8. typedef long long ll;
  9. const ll mod=998244353;
  10. const int maxn=100010;
  11. int n,m,tot;
  12. ll inv(ll x)
  13. {
  14. ll z=1,y=mod-2;
  15. while(y)
  16. {
  17. if(y&1) z=z*x%mod;
  18. x=x*x%mod,y>>=1;
  19. }
  20. return z;
  21. }
  22. ll calc(ll a,ll b)
  23. {
  24. return z(a*b+(1-a)*(1-b));
  25. }
  26. int rd()
  27. {
  28. int ret=0; char gc=getchar();
  29. while(gc<'0'||gc>'9') gc=getchar();
  30. while(gc>='0'&&gc<='9') ret=ret*10+gc-'0',gc=getchar();
  31. return ret;
  32. }
  33. int ls[maxn<<8],rs[maxn<<8],rt[maxn<<2];
  34. ll s[maxn<<8];
  35. void up1(int l,int r,int &x,int a,int b,ll c)
  36. {
  37. if(!x) x=++tot,s[x]=1;
  38. if(a<=l&&r<=b)
  39. {
  40. s[x]=calc(s[x],c);
  41. return ;
  42. }
  43. int mid=l+r>>1;
  44. if(a<=mid) up1(l,mid,ls[x],a,b,c);
  45. if(b>mid) up1(mid+1,r,rs[x],a,b,c);
  46. }
  47. ll q1(int l,int r,int x,int a)
  48. {
  49. if(!x) return 1;
  50. if(l==r) return s[x];
  51. int mid=l+r>>1;
  52. if(a<=mid) return calc(s[x],q1(l,mid,ls[x],a));
  53. else return calc(s[x],q1(mid+1,r,rs[x],a));
  54. }
  55. void up2(int l,int r,int x,int a,int b,int c,int d,ll e)
  56. {
  57. if(a<=l&&r<=b)
  58. {
  59. up1(1,n,rt[x],c,d,e);
  60. return ;
  61. }
  62. int mid=l+r>>1;
  63. if(a<=mid) up2(l,mid,lson,a,b,c,d,e);
  64. if(b>mid) up2(mid+1,r,rson,a,b,c,d,e);
  65. }
  66. ll q2(int l,int r,int x,int a,int b)
  67. {
  68. if(l==r) return q1(1,n,rt[x],b);
  69. int mid=l+r>>1;
  70. if(a<=mid) return calc(q1(1,n,rt[x],b),q2(l,mid,lson,a,b));
  71. else return calc(q1(1,n,rt[x],b),q2(mid+1,r,rson,a,b));
  72. }
  73. int main()
  74. {
  75. n=rd(),m=rd();
  76. int i,a,b,c;
  77. ll p,q;
  78. for(i=1;i<=m;i++)
  79. {
  80. c=rd(),a=rd(),b=rd();
  81. if(c==1)
  82. {
  83. p=inv(b-a+1);
  84. if(a>1) up2(0,n,1,1,a-1,a,b,z(1-p)),up2(0,n,1,0,0,0,a-1,0);
  85. if(b<n) up2(0,n,1,a,b,b+1,n,z(1-p)),up2(0,n,1,0,0,b+1,n,0);
  86. up2(0,n,1,a,b,a,b,z(1-2*p)),up2(0,n,1,0,0,a,b,p);
  87. }
  88. else printf("%lld\n",q2(0,n,1,a-1,b));
  89. }
  90. return 0;
  91. }

【BZOJ4785】[Zjoi2017]树状数组 树套树(二维线段树)的更多相关文章

  1. tyvj P1716 - 上帝造题的七分钟 二维树状数组区间查询及修改 二维线段树

    P1716 - 上帝造题的七分钟 From Riatre    Normal (OI)总时限:50s    内存限制:128MB    代码长度限制:64KB 背景 Background 裸体就意味着 ...

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

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

  3. 二维线段树 poj-2155

    题意:t组样例 ,输入 n,m,表示n*n的矩阵进行m次操作 ,C: 输入两个坐标 ,组成的矩形 进行取反操作 ,Q:对输的坐标位置输入其值. 思路:一开始想的是用1000(表示x轴)个线段树(对每段 ...

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

    可以发现这个写挂的树状数组求的是后缀和.find(r)-find(l-1)在模2意义下实际上查询的是l-1~r-1的和,而本来要查询的是l~r的和.也就是说,若结果正确,则a[l-1]=a[r](mo ...

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

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

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

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

  7. bzoj4785:[ZJOI2017]树状数组:二维线段树

    分析: "如果你对树状数组比较熟悉,不难发现可怜求的是后缀和" 设数列为\(A\),那么可怜求的就是\(A_{l-1}\)到\(A_{r-1}\)的和(即\(l-1\)的后缀减\( ...

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

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

  9. BZOJ.4553.[HEOI2016&TJOI2016]序列(DP 树状数组套线段树/二维线段树(MLE) 动态开点)

    题目链接:BZOJ 洛谷 \(O(n^2)\)DP很好写,对于当前的i从之前满足条件的j中选一个最大值,\(dp[i]=d[j]+1\) for(int j=1; j<i; ++j) if(a[ ...

随机推荐

  1. 粗谈CDN

    CDN:内容分发网络(content delivery network) 1.DNS解析到最快(有可能是地理上最近,也有可能是地理上远但是链路最好)的CDN缓存设备 2.从CDN获取已经缓存的资源 3 ...

  2. QQ-weiyun(微云)-云储存

    ylbtech-DatabaseDesgin:QQ-weiyun(微云)-云储存 1.A,数据库关系图(Database Diagram) -- =========================== ...

  3. S5PV210之内外存学习

    RAM,内部存储器,用来运行程序(DRAM,SRAM,DDR) ROM,外部存储器,存储数据.程序(硬盘,FLASH等) 内存:SRAM,静态内存,容量下,价格高,不需要初始化,上电后直接使用 DRA ...

  4. Tomcat Deployment failure ,locked one or more files

    在用Eclipse+Tomcat配置J2EE项目时,出现如下提示错误: Undeployment Failure could not be redeployed because it could no ...

  5. jqGrid怎么设置初始化页面时不加载数据(不向服务器请求数据)

    最近做一些表格一直用到jqGrid,今天遇到一个问题: 1.就是页面加载的时候数据不显示,点击搜索才根据请求从服务器返回并显示内容. 2.默认不从服务器请求数据(不然在开发者工具下会显示请求不到数据的 ...

  6. [ACM] POJ 1035 Spell checker (单词查找,删除替换添加不论什么一个字母)

    Spell checker Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 18693   Accepted: 6844 De ...

  7. 为什么重写equals方法时,要求必须重写hashCode方法?

    1 equals方法 Object类中默认的实现方式是  :   return this == obj  .那就是说,只有this 和 obj引用同一个对象,才会返回true. 而我们往往需要用equ ...

  8. IntelliJ IDEA 取消控制台行数限制

    在idea7之后的版本中取消了 控制台行数设置 选项,只能通过更改配置文件进行更改 在%安装目录%/bin中找到idea.properties文件,更改idea.cycle.buffer.size项值 ...

  9. 使用Spring开发和监控线程池服务

    第1步:添加maven 项目 第2步:添加依赖库 将Spring的依赖添加到Maven的pom.xml文件中. 1 2 3 4 5 6 7 8 9 10 11 <!-- Spring 3 dep ...

  10. [Algorithms] Using Dynamic Programming to Solve longest common subsequence problem

    Let's say we have two strings: str1 = 'ACDEB' str2 = 'AEBC' We need to find the longest common subse ...