「ZJOI2019」线段树

听说有人喷这个题简单,然后我就跑去做,然后自闭感++,rp++(雾)


理性分析一波,可以发现最后形成的\(2^k\)个线段树,对应的操作的一个子集,按时间顺序作用到这颗线段树上。

首先考虑研究一下tag的性质,比如两个操作时间先后是否没有影响,操作是否可以以某种形式进行合并,然后啥也没发现。

然后考虑一下一颗树是否可以被压成某个状态,比如实际上只有\(\log\)个状态然后去dp,发现也不行

再次冷静分析一波,发现好像每个节点可以独立考虑,结合上面\(2^n\),不妨考虑转换成概率,算出每个点被打上tag的概率,在数值上同时也是它的期望,这样我们最后乘上线段树个数就好了。

然后这时候就只对一个线段树操作了,考虑咋去搞

这差不多是我做题时的心路历程,但是我写了好几个做法都过不了大样例,十分自闭,总是以为自己没想清楚哪里假了之类的赶紧改过来,结果大样例输出了十几个不同的答案

心态崩了.jpg

又被什么pkuthusc的报名表填的心烦意乱的,就直接去看题解了

发现自己压根没有对节点分类讨论的意识...

设\(dp_i\)表示\(i\)节点被打上标记的概率

考虑对区间\([4,6]\)进行操作后,节点的改变。

后文中,原来的一半因为不变,所以节点改变指变化的那一半的改变。

  • 红色点

    有标记也全部被push了

    \[dp_i=\frac{dp_i}{2}
    \]

  • 绿色点

    一定被打上标记了

    \[dp_i=\frac{dp_i+1}{2}
    \]

  • 黑色点

    考虑它到跟的红色点是不是有一个点给它放标记了,或者原本是它自己的

    这个没法直接搞,定义一个辅助数组\(f_i\)表示\(i\)到根至少有一个点有标记的概率

    那么有

    \[dp_i=\frac{dp_i+f_i}{2}
    \]

  • 白色点不变

那考虑球一下辅助数组\(f\)

  • 红色点

    直接没了

    \[f_i=\frac{f_i}{2}
    \]

  • 绿色点

    发现绿色点的子树,都有了一个绿色点,即

    \[f_i=\frac{f_i+1}{2}
    \]

    需要实现一个子树加

    我们发现形如\(x\leftarrow\frac{x+1}{2}\)的式子,如果进行\(n\)次,会变成\(\frac{x+2^n-1}{2^n}\)

    于是可以直接打标记记录一下次数

  • 灰色点

    发现有就放到灰色点,没有仍然没有,所以灰色点的子树不变

总复杂度\(O(n\log n)\)


Code:

  1. #include <cstdio>
  2. #include <cctype>
  3. const int SIZE=1<<21;
  4. char ibuf[SIZE],*iS,*iT;
  5. #define gc() (iS==iT?(iT=(iS=ibuf)+fread(ibuf,1,SIZE,stdin),iS==iT?EOF:*iS++):*iS++)
  6. template <class T>
  7. void read(T &x)
  8. {
  9. x=0;char c=gc();
  10. while(!isdigit(c)) c=gc();
  11. while(isdigit(c)) x=x*10+c-'0',c=gc();
  12. }
  13. const int mod=998244353;
  14. const int inv2=499122177;
  15. const int N=1e5+10;
  16. #define mul(a,b) (1ll*(a)*(b)%mod)
  17. inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
  18. #define ls id<<1
  19. #define rs id<<1|1
  20. int fdp[N<<2],dp[N<<2],sum[N<<2],tag[N<<2];
  21. int n,m,in2[N];
  22. void updata(int id)
  23. {
  24. sum[id]=add(add(sum[ls],sum[rs]),dp[id]);
  25. }
  26. void pushdown(int id)
  27. {
  28. tag[ls]+=tag[id];
  29. tag[rs]+=tag[id];
  30. fdp[ls]=add(mul(fdp[ls],in2[tag[id]]),add(1,mod-in2[tag[id]]));
  31. fdp[rs]=add(mul(fdp[rs],in2[tag[id]]),add(1,mod-in2[tag[id]]));
  32. tag[id]=0;
  33. }
  34. void modify(int id,int L,int R,int l,int r)
  35. {
  36. if(l==L&&r==R)
  37. {
  38. sum[id]=add(sum[id],mod-dp[id]);
  39. dp[id]=mul(dp[id]+1,inv2);
  40. fdp[id]=mul(fdp[id]+1,inv2);
  41. sum[id]=add(sum[id],dp[id]);
  42. ++tag[id];
  43. return;
  44. }
  45. dp[id]=mul(dp[id],inv2);
  46. fdp[id]=mul(fdp[id],inv2);
  47. pushdown(id);
  48. int Mid=L+R>>1;
  49. if(r<=Mid)
  50. {
  51. sum[rs]=add(sum[rs],mod-dp[rs]);
  52. dp[rs]=mul(add(dp[rs],fdp[rs]),inv2);
  53. sum[rs]=add(sum[rs],dp[rs]);
  54. modify(ls,L,Mid,l,r);
  55. }
  56. else if(l>Mid)
  57. {
  58. sum[ls]=add(sum[ls],mod-dp[ls]);
  59. dp[ls]=mul(add(dp[ls],fdp[ls]),inv2);
  60. sum[ls]=add(sum[ls],dp[ls]);
  61. modify(rs,Mid+1,R,l,r);
  62. }
  63. else
  64. modify(ls,L,Mid,l,Mid),modify(rs,Mid+1,R,Mid+1,r);
  65. updata(id);
  66. }
  67. int main()
  68. {
  69. read(n),read(m);
  70. in2[0]=1;
  71. for(int i=1;i<=n;i++) in2[i]=mul(in2[i-1],inv2);
  72. for(int t=1,op,l,r,i=1;i<=m;i++)
  73. {
  74. read(op);
  75. if(op==1)
  76. {
  77. read(l),read(r);
  78. modify(1,1,n,l,r);
  79. t=add(t,t);
  80. }
  81. else printf("%lld\n",mul(sum[1],t));
  82. }
  83. return 0;
  84. }

2019.5.10

「ZJOI2019」线段树 解题报告的更多相关文章

  1. 【LOJ】#3043. 「ZJOI2019」线段树

    LOJ#3043. 「ZJOI2019」线段树 计数转期望的一道好题-- 每个点设两个变量\(p,q\)表示这个点有\(p\)的概率有标记,有\(q\)的概率到祖先的路径上有个标记 被覆盖的点$0.5 ...

  2. 「SHOI2014」三叉神经树 解题报告

    「SHOI2014」三叉神经树 膜拜神仙思路 我们想做一个类似于动态dp的东西,首先得确保我们的运算有一个交换律,这样我们可以把一长串的运算转换成一块一块的放到矩阵上之类的东西,然后拿数据结构维护. ...

  3. LOJ 3043: 洛谷 P5280: 「ZJOI2019」线段树

    题目传送门:LOJ #3043. 题意简述: 你需要模拟线段树的懒标记过程. 初始时有一棵什么标记都没有的 \(n\) 阶线段树. 每次修改会把当前所有的线段树复制一份,然后对于这些线段树实行一次区间 ...

  4. 「ZJOI2019」线段树

    传送门 Description 线段树的核心是懒标记,下面是一个带懒标记的线段树的伪代码,其中 tag 数组为懒标记: 其中函数\(Lson(Node)\)表示\(Node\)的左儿子,\(Rson( ...

  5. 【LOJ3043】「ZJOI2019」线段树

    题面 问题可以转化为每次区间覆盖操作有 \(\frac{1}{2}\) 的概率进行,求标记和的期望.于是我们只要求出所有点有标记的概率即可. 我们设 \(f_i\) 表示节点 \(i\) 有标记的概率 ...

  6. @loj - 3043@「ZJOI2019」线段树

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 九条可怜是一个喜欢数据结构的女孩子,在常见的数据结构中,可怜最喜 ...

  7. 「模板」 线段树——区间乘 && 区间加 && 区间求和

    「模板」 线段树--区间乘 && 区间加 && 区间求和 原来的代码太恶心了,重贴一遍. #include <cstdio> int n,m; long l ...

  8. Loj #2570. 「ZJOI2017」线段树

    Loj #2570. 「ZJOI2017」线段树 题目描述 线段树是九条可怜很喜欢的一个数据结构,它拥有着简单的结构.优秀的复杂度与强大的功能,因此可怜曾经花了很长时间研究线段树的一些性质. 最近可怜 ...

  9. 「ZJOI2016」大森林 解题报告

    「ZJOI2016」大森林 神仙题... 很显然线段树搞不了 考虑离线操作 我们只搞一颗树,从位置1一直往后移动,然后维护它的形态试试 显然操作0,1都可以拆成差分的形式,就是加入和删除 因为保证了操 ...

随机推荐

  1. 【leetcode】976. Largest Perimeter Triangle

    题目如下: Given an array A of positive lengths, return the largest perimeter of a triangle with non-zero ...

  2. UNP学习第九章 基本名字与地址转换

    之前都用数值地址来表示主机(206.6.226.33),用数值端口号来标识服务器. 然而,我们应该使用名字而不是数值:名字比较容易记,数值地址可以改变但名字保持不变. 随着往IPv6上转移,数值地址变 ...

  3. apue 第10章 信号signal

    每种信号都有名字,都是以SIG开头 信号机制最简单的接口是signal函数 #include <signal.h> typedef void (*sighandler_t)(int); s ...

  4. 低价购买 (动态规划,变种最长下降子序列(LIS))

    题目描述 “低价购买”这条建议是在奶牛股票市场取得成功的一半规则.要想被认为是伟大的投资者,你必须遵循以下的问题建议:“低价购买:再低价购买”.每次你购买一支股票,你必须用低于你上次购买它的价格购买它 ...

  5. loj2009. 「SCOI2015」小凸玩密室

    「SCOI2015」小凸玩密室 小凸和小方相约玩密室逃脱,这个密室是一棵有 $ n $ 个节点的完全二叉树,每个节点有一个灯泡.点亮所有灯泡即可逃出密室.每个灯泡有个权值 $ A_i $,每条边也有个 ...

  6. xcode Delete current line

    Delete a line like eclipse CTRL+D (tested on Xcode 4.5.1) : First of all, change these rights : sudo ...

  7. JAVA Web学习笔记

    JAVA Web学习笔记 1.JSP (java服务器页面) 锁定 本词条由“科普中国”百科科学词条编写与应用工作项目 审核 . JSP全名为Java Server Pages,中文名叫java服务器 ...

  8. C++之前置自增与后置自增

    关于前置自增与后置自增的区别我是参考这里:http://bbs.bccn.net/thread-454977-1-1.html 简单复述下,比如++x; 与 x++; 在C中,++x这个表达式的值为原 ...

  9. python 使用yaml模块

    python:yaml模块一.yaml文件介绍YAML是一种简洁的非标记语言.其以数据为中心,使用空白,缩进,分行组织数据,从而使得表示更加简洁.1. yaml文件规则基本规则:    大小写敏感   ...

  10. java的继承 和super关键字 构造器

    面向对象的特性二继承: 继承的好处: 1.减少代码的冗余.提高了代码的复用性 2.便于功能的扩展 3.为之后多态的使用,提供了前提 继承的格式: class A extends B{} A:子类.派生 ...