4785: [Zjoi2017]树状数组

Time Limit: 40 Sec  Memory Limit: 512 MB
Submit: 297  Solved: 195
[Submit][Status][Discuss]

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,因此第一次询问可怜一定正确,第二次询问可怜一定错误。

首先经过分析证明可得,树状数组只是一层外衣,实际上题目就是求[l-1,r-1]和[l,r]的改变次数的差为偶数的概率,也就是l-1和r改变次数差为偶数的概率。(l==1的情况要特殊处理,也就是[1,r-1]和[r+1,n]的总改变次数差为偶数的概率)

想到这里之后,我们会有一个看似正确的直觉:可以通过动规+前缀和求出每个数被改变奇数次和偶数次的概率。但是实际上由于动规方程里的并不是互斥事件,所以概率不可以直接相乘。

所以我们可以肯定,一定是对每个数,依次遍历所有的修改,已修改时间为下标做DP。这样就有了一个简单的50分做法。

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<algorithm>
  5. #define rep(i,l,r) for (int i=l; i<=r; i++)
  6. typedef long long ll;
  7. using namespace std;
  8.  
  9. const int N=,md=;
  10. int dp[N],n,m,op,l,r,cnt;
  11. struct node{ int l,r,v; }G[N];
  12.  
  13. int ksm(int x,int y){
  14. int res=;
  15. for(; y; y>>=,x=(ll)x*x%md)
  16. if (y&) res=(ll)res*x%md;
  17. return res;
  18. }
  19.  
  20. int main(){
  21. freopen("bit.in","r",stdin);
  22. freopen("bit.out","w",stdout);
  23. scanf("%d%d",&n,&m);
  24. while (m--){
  25. scanf("%d%d%d",&op,&l,&r);
  26. if (op==) G[cnt++]=(node){l,r,ksm(r-l+,md-)};
  27. else{
  28. int ans=;
  29. if(l==){
  30. rep(i,,cnt)
  31. if(G[i].r>=l){
  32. int len=(G[i].r-max(l,G[i].l)+-(G[i].r>=r&&G[i].l<=r));
  33. len = len*(ll)G[i].v%md;
  34. ans=(((ll)ans*(-len)+(ll)(-ans)*len)%md+md)%md;
  35. }
  36. }else
  37. rep(i,,cnt)
  38. if(G[i].l<=l-&&G[i].r>=r){
  39. int len=G[i].v*(ll)%md;
  40. ans=(((ll)ans*(-len)+(ll)(-ans)*len)%md+md)%md;
  41. }else if((G[i].l<=l-&&G[i].r>=l-)||(G[i].l<=r&&G[i].r>=r)){
  42. int len=G[i].v;
  43. ans=(((ll)ans*(-len)+(ll)(-ans)*len)%md+md)%md;
  44. }
  45. printf("%d\n",ans);
  46. }
  47. }
  48. return ;
  49. }

那么如果想到了这里,已经不难进一步想出用数据结构来维护了。将每个询问映射成二维平面上的点(l-1,r),(类似BZOJ4826影魔),然后用二维线段树实现矩形区间修改,l==1的情况只要一维线段树即可。

UOJ上可能有BUG,在线自定义测试的时候程序正常运行并返回正确结果,同样的数据提交评测却无限RE。能过官方数据,BZOJ上AC,UOJ上的HACK数据没有测。

现在总结一下写代码的时候需要注意的地方。

1.每写完一段停下来检查一下

2.修改程序的时候要慢一点,检查修改是否正确并思考是否有类似地方需要修改。

3.调试时多想想平时遇到的问题优先考虑。

4.多总结遇到的错误(特别是模板方面),以1A为最终目标。

  1. #include<cstdio>
  2. #include<algorithm>
  3. #define lc (x<<1)
  4. #define rc ((x<<1)|1)
  5. #define rep(i,l,r) for (int i=l; i<=r; i++)
  6. using namespace std;
  7.  
  8. const int N=,md=;
  9. int n,m,cnt,nd,op,l,r,rt[N*],P[N*];
  10. struct D{ int ls,rs; }a[N*];
  11. int F(int x,int y){ return (1ll*x*y%md+1ll*(-x+md)*(-y+md)%md)%md; }
  12.  
  13. int ksm(int a,int b){
  14. int res;
  15. for (res=; b; a=(1ll*a*a)%md,b>>=)
  16. if (b & ) res=(1ll*res*a)%md;
  17. return res;
  18. }
  19.  
  20. void mdf(int &x,int L,int R,int l,int r,int k){
  21. if (!x) x=++nd,P[x]=;
  22. if (L==l && r==R) { P[x]=F(P[x],k); return; }
  23. int mid=(L+R)>>;
  24. if (r<=mid) mdf(a[x].ls,L,mid,l,r,k);
  25. else if (l>mid) mdf(a[x].rs,mid+,R,l,r,k);
  26. else mdf(a[x].ls,L,mid,l,mid,k),mdf(a[x].rs,mid+,R,mid+,r,k);
  27. }
  28.  
  29. int que(int x,int L,int R,int pos){
  30. if (!x) return ;
  31. if (L==R) return P[x]; int mid=(L+R)>>;
  32. if (pos<=mid) return F(P[x],que(a[x].ls,L,mid,pos));
  33. else return F(P[x],que(a[x].rs,mid+,R,pos));
  34. }
  35.  
  36. void Mdf(int x,int L,int R,int l,int r,int xx,int yy,int k){
  37. if (L==l && r==R){ mdf(rt[x],,n+,xx,yy,k); return; }
  38. int mid=(L+R)>>;
  39. if (r<=mid) Mdf(lc,L,mid,l,r,xx,yy,k);
  40. else if (l>mid) Mdf(rc,mid+,R,l,r,xx,yy,k);
  41. else Mdf(lc,L,mid,l,mid,xx,yy,k),Mdf(rc,mid+,R,mid+,r,xx,yy,k);
  42. }
  43.  
  44. int Que(int x,int L,int R,int posx,int posy){
  45. int res=;
  46. if (rt[x]) res=F(res,que(rt[x],,n+,posy));
  47. if (L==R) return res; int mid=(L+R)>>;
  48. if (posx<=mid) res=F(res,Que(lc,L,mid,posx,posy));
  49. else res=F(res,Que(rc,mid+,R,posx,posy));
  50. return res;
  51. }
  52.  
  53. int main(){
  54. freopen("bit.in","r",stdin);
  55. freopen("bit.out","w",stdout);
  56. scanf("%d%d",&n,&m);
  57. rep(i,,m){
  58. scanf("%d%d%d",&op,&l,&r);
  59. if (op==){
  60. int p=ksm(r-l+,md-);
  61. if (l>) Mdf(,,n,,l-,l,r,(-p+md)%md),mdf(rt[],,n,,l-,);
  62. if (r<n) Mdf(,,n,l,r,r+,n,(-p+md)%md),mdf(rt[],,n,r+,n,);
  63. if (l!=r) Mdf(,,n,l,r,l,r,(-p*+md+md)%md);
  64. mdf(rt[],,n,l,r,p);
  65. }else{
  66. if (l==) printf("%d\n",que(rt[],,n,r)); else printf("%d\n",Que(,,n,l-,r));
  67. }
  68. }
  69. return ;
  70. }

[BZOJ4785][ZJOI2017]树状数组(概率+二维线段树)的更多相关文章

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

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

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

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

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

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

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

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

  5. luogu3380/bzoj3196 二逼平衡树 (树状数组套权值线段树)

    带修改区间K大值 这题有很多做法,我的做法是树状数组套权值线段树,修改查询的时候都是按着树状数组的规则找出那log(n)个线段树根,然后一起往下做 时空都是$O(nlog^2n)$的(如果离散化了的话 ...

  6. CF1093E Intersection of Permutations 树状数组套权值线段树

    \(\color{#0066ff}{ 题目描述 }\) 给定整数 \(n\) 和两个 \(1,\dots,n\) 的排列 \(a,b\). \(m\) 个操作,操作有两种: \(1\ l_a\ r_a ...

  7. BZOJ2141排队——树状数组套权值线段树(带修改的主席树)

    题目描述 排排坐,吃果果,生果甜嗦嗦,大家笑呵呵.你一个,我一个,大的分给你,小的留给我,吃完果果唱支歌,大家 乐和和.红星幼儿园的小朋友们排起了长长地队伍,准备吃果果.不过因为小朋友们的身高有所区别 ...

  8. Dynamic Rankings(树状数组套权值线段树)

    Dynamic Rankings(树状数组套权值线段树) 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[ ...

  9. [BZOJ 3295] [luogu 3157] [CQOI2011]动态逆序对(树状数组套权值线段树)

    [BZOJ 3295] [luogu 3157] [CQOI2011] 动态逆序对 (树状数组套权值线段树) 题面 给出一个长度为n的排列,每次操作删除一个数,求每次操作前排列逆序对的个数 分析 每次 ...

随机推荐

  1. Attention-over-Attention Neural Networks for Reading Comprehension论文总结

    Attention-over-Attention Neural Networks for Reading Comprehension 论文地址:https://arxiv.org/pdf/1607.0 ...

  2. 服务器部署之nginx的配置

    nginx可作为Web和 反向代理 服务器,在高连接并发的情况下,Nginx是Apache服务器不错的替代品.下面记录一下自己对nginx的配置和使用. nginx的安装 环境:oracle-linu ...

  3. 70.如何在xilinx SDK中显示行号

    Window→preferences→editor→test editor 对ecilpse的通用方法 打开Eclipse软件,在菜单中选择窗口——首选项,打开新的窗口. 在新的窗口中依次选择常规—— ...

  4. python 并发爬虫的快感

    import time from tomorrow import threads from requests_html import HTMLSession session=HTMLSession() ...

  5. 2017百度春招<有趣的排序>

    题目 度度熊有一个N个数的数组,他想将数组从小到大排好序,但是萌萌的度度熊只会下面这个操作:任取数组中的一个数然后将它放置在数组的最后一个位置.问最少操作多少次可以使得数组从小到大有序? #inclu ...

  6. 正则表达式对IP地址的限制

    正则表达式 ^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[ ...

  7. 爬虫基础库之beautifulsoup的简单使用

    beautifulsoup的简单使用 简单来说,Beautiful Soup是python的一个库,最主要的功能是从网页抓取数据.官方解释如下: ''' Beautiful Soup提供一些简单的.p ...

  8. 小程序授权怎么写 , 用户点击取消授权 调用 wx.authorize

    点击获取授权 onLoad: function (options) { console.log("onLoad====="); var that=this; wx.getUserI ...

  9. Yii2使用驼峰命名的形式访问控制器

    yii2在使用的时候,访问控制器的时候,如果控制器的名称是驼峰命名法,那访问的url中要改成横线的形式.例如: public function actionRoomUpdate() { // }//访 ...

  10. python开发学习-day03(set集合、collection系列 、深浅拷贝、函数)

    s12-20160116-day03 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: ...