洛谷 P5280 - [ZJOI2019]线段树(线段树+dp,神仙题)
神仙 ZJOI,不会做啊不会做/kk
Sooke:“这八成是考场上最可做的题”,由此可见 ZJOI 之毒瘤。
首先有一个非常显然的转化,就是题目中的“将线段树分裂成两棵线段树”,我们事实上大可不必真的把线段树一分为二,可以看作对于操作集合 \(S\) 的所有子集 \(S'\subseteq S\) 计算出执行 \(S'\) 中的操作后线段树上有多少个节点 tag 为 \(1\)。
其次建好线段树,我们考虑一次操作 \([l,r]\) 会对哪些节点产生影响。不妨举个例子,比方说 \(n=6,l=3,r=4\),那么建出线段树来就如下图所示:

不难发现:
- 对于图中标浅蓝色的节点,在递归过程中会被遍历到,它们的标记一定会被推向它们的左右儿子,故此次修改结束后它们的 \(tag\) 一定为 \(0\)。我们不妨称之为Ⅰ类点。
- 对于图中标红色的节点,它们所表示的区间完全包含于 \([l,r]\) 中,也就是说,当我们递归到这样的节点时我们会在它上面打上 \(1\) 的 \(tag\) 并返回。故此次修改结束后它们的 \(tag\) 一定为 \(1\)。我们不妨称之为Ⅱ类点。
可我们一次修改只会对这两类点的 \(tag\) 值产生影响吗?
注意到上图中还有一类点被我们用绿色标了出来,对于这样的节点,虽然我们不会直接访问它们,但它们的祖先全部都是Ⅰ类点,故我们在标记下传的过程中,如果存在它的某个 \(tag\) 为 \(1\) 祖先,那么该祖先的 \(tag\) 一定会传到该节点。我们如法炮制地称这样的节点为Ⅲ类点。
我们把每次修改影响的节点弄清楚了,可怎么计算答案呢?
考虑 \(dp\),可以非常自然地想到 \(dp_i\) 表示当前状态下有多少个操作子集使节点 \(i\) 的 \(tag\) 为 \(1\)。不过直接 \(dp\) 似乎是不太可行的(也不是不行,只是细节多一些,因为要下传一些标记),这里用到了一个我想不到的套路——方案数转期望,也就是说我们将每个 \(dp_i\) 都除以 \(2^{|S|}\),其中 \(S\) 为当前操作集合。最终得到的 \(dp_i\) 显然是随便选一个集合,使得 \(tag_i=1\) 的概率,那么最终的答案显然为 \(\sum dp_i\times 2^{|S|}\)。
接下来考虑 \(dp_i\) 的转移:
对于Ⅰ类点,有 \(1/2\) 的概率不执行该操作,\(tag\) 保持不变,有 \(1/2\) 的概率执行该操作,\(tag\) 保持变为 \(0\),故 \(dp_i=\dfrac{dp_i}{2}\)
对于Ⅱ类点,有 \(1/2\) 的概率不执行该操作,\(tag\) 保持不变,有 \(1/2\) 的概率执行该操作,\(tag\) 保持变为 \(1\),故 \(dp_i=\dfrac{dp_i+1}{2}\)
对于Ⅲ类点,有 \(1/2\) 的概率不执行该操作,\(tag\) 保持不变,有 \(1/2\) 的概率执行该操作,如果 \(i\) 到根节点的路径上存在某个 \(tag\) 为 \(1\) 的点那么该节点的 \(tag\) 变为 \(1\),否则 \(tag\) 保持为 \(0\)。故我们考虑引入一个新的量 \(f_i\) 表示当前状态下有多大概率存在 \(i\) 到根节点路径上的某个点 \(j\) 满足 \(tag_j=1\),如果我们已经求出了 \(f_i\),那么显然有 \(dp_i=\dfrac{dp_i+f_i}{2}\)。
对于其他节点,显然执行不执行该操作对该节点的 \(tag\) 没有影响,故 \(dp_i\) 保持不变。
注意到这三类节点的个数加起来是 \(\log n\) 级别的(因为Ⅰ类点 \(+\) Ⅱ类点个数是 \(\log n\) 级别的,而每个Ⅰ类点最多贡献 \(1\) 个Ⅲ类点),故直接计算 \(dp\) 是没问题的,于是问题转化为怎样求 \(f_i\)。
- 对于Ⅰ类点,有 \(1/2\) 的概率不执行该操作,概率保持不变,有 \(1/2\) 的概率执行该操作,它到根节点路径上所有节点的 \(tag\) 都会变为 \(0\),故 \(f_i=\dfrac{f_i}{2}\)
- 对于Ⅱ类点,有 \(1/2\) 的概率不执行该操作,概率保持不变,有 \(1/2\) 的概率执行该操作,它自身的 \(tag\) 就变为了 \(1\),故 \(f_i=\dfrac{f_i+1}{2}\)。
- 对于Ⅲ类点,不难发现操作本质实际上是将其到根节点路径上的 \(tag\) 转移到该节点上,故 \(f_i\) 保持不变。
对于不属于这三类节点的其他节点,我们又可将其分为两类——在Ⅲ类节点子树中的节点和在Ⅱ类节点子树中的节点:
- 在Ⅲ类节点子树中的节点,效仿计算Ⅲ类节点 \(f_i\) 的变化情况的过程可知这样的节点的 \(f\) 值保持不变。
- 在Ⅱ类节点子树中的节点,有 \(1/2\) 的概率不执行该操作,概率保持不变,有 \(1/2\) 的概率执行该操作,这样一来其到根节点的路径上一定存在某个节点(当前考虑的Ⅱ类节点)\(tag\) 为 \(1\),故 \(f_i=\dfrac{f_i+1}{2}\)。
考虑每次操作维护一个懒标记 \(lz\) 表示当前区间中的节点进行了 \(lz\) 次 \(f_i=\leftarrow\dfrac{f_i+1}{2}\) 次操作,显然经过 \(lz\) 次操作后 \(f_i\) 会变为 \(\dfrac{f_i+2^{lz}-1}{2^{lz}}\),故可以做到 \(\mathcal O(1)\) 下推懒标记。于是这题就做完了。
时间复杂度线对。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=1e5;
const int INV2=499122177;
const int MOD=998244353;
int n,qu,pw2[MAXN+5],pw2_1[MAXN+5],inv2[MAXN+5];
struct node{int l,r,dp,sdp,lz,sum;} s[MAXN*8+5];
void pushup(int k){s[k].sum=((s[k<<1].sum+s[k<<1|1].sum)%MOD+s[k].dp)%MOD;}
void pushdown(int k){
s[k<<1].sdp=1ll*(s[k<<1].sdp+pw2[s[k].lz]-1)*inv2[s[k].lz]%MOD;
s[k<<1|1].sdp=1ll*(s[k<<1|1].sdp+pw2[s[k].lz]-1)*inv2[s[k].lz]%MOD;
s[k<<1].lz+=s[k].lz;s[k<<1|1].lz+=s[k].lz;s[k].lz=0;
}
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;if(l==r) return;
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void modify(int k,int l,int r){
if(r<s[k].l||l>s[k].r){
s[k].dp=1ll*(s[k].dp+s[k].sdp)*INV2%MOD;
pushup(k);return;
}
if(l<=s[k].l&&s[k].r<=r){
s[k].dp=1ll*(s[k].dp+1)*INV2%MOD;
s[k].sdp=1ll*(s[k].sdp+1)*INV2%MOD;
s[k].lz++;pushup(k);return;
}
s[k].dp=1ll*s[k].dp*INV2%MOD;s[k].sdp=1ll*s[k].sdp*INV2%MOD;
pushdown(k);modify(k<<1,l,r);modify(k<<1|1,l,r);
pushup(k);
}
void iterate(int k){
// printf("%d %d %d %d %d\n",k,s[k].l,s[k].r,s[k].dp,s[k].sdp);
if(s[k].l==s[k].r) return;pushdown(k);iterate(k<<1);iterate(k<<1|1);
}
int main(){
scanf("%d%d",&n,&qu);int qq=0;
pw2[0]=1;for(int i=1;i<=MAXN;i++) pw2[i]=pw2[i-1]*2%MOD;
inv2[0]=1;for(int i=1;i<=MAXN;i++) inv2[i]=1ll*inv2[i-1]*INV2%MOD;
build(1,1,n);
while(qu--){
int opt;scanf("%d",&opt);
if(opt==1){int l,r;scanf("%d%d",&l,&r);modify(1,l,r);qq++;}
else printf("%d\n",1ll*s[1].sum*pw2[qq]%MOD);
}
return 0;
}
洛谷 P5280 - [ZJOI2019]线段树(线段树+dp,神仙题)的更多相关文章
- 洛谷P5280 [ZJOI2019]线段树
https://www.luogu.org/problemnew/show/P5280 省选的时候后一半时间开这题,想了接近两个小时的各种假做法,之后想的做法已经接近正解了,但是有一些细节问题理不 ...
- 洛谷P5280 [ZJOI2019]线段树 [线段树,DP]
传送门 无限Orz \(\color{black}S\color{red}{ooke}\)-- 思路 显然我们不能按照题意来每次复制一遍,而多半是在一棵线段树上瞎搞. 然后我们可以从\(modify\ ...
- 洛谷P5280 [ZJOI2019]线段树(线段树)
题面 传送门 题解 考场上就这么一道会做的其它连暴力都没打--活该爆炸-- 首先我们得看出问题的本质:有\(m\)个操作,总共\(2^m\)种情况分别对应每个操作是否执行,求这\(2^m\)棵线段树上 ...
- 洛谷 P2622 关灯问题II(状压DP入门题)
传送门 https://www.cnblogs.com/violet-acmer/p/9852294.html 题解: 相关变量解释: int n,m; ];//a[i][j] : 第i个开关对第j个 ...
- 洛谷 P3373 【模板】线段树 2
洛谷 P3373 [模板]线段树 2 洛谷传送门 题目描述 如题,已知一个数列,你需要进行下面三种操作: 将某区间每一个数乘上 xx 将某区间每一个数加上 xx 求出某区间每一个数的和 输入格式 第一 ...
- 【BZOJ3244】【NOI2013】树的计数(神仙题)
[BZOJ3244][NOI2013]树的计数(神仙题) 题面 BZOJ 这题有点假,\(bzoj\)上如果要交的话请输出\(ans-0.001,ans,ans+0.001\) 题解 数的形态和编号没 ...
- 洛谷P1067 多项式输出 NOIP 2009 普及组 第一题
洛谷P1067 多项式输出 NOIP 2009 普及组 第一题 题目描述 一元n次多项式可用如下的表达式表示: 输入输出格式 输入格式 输入共有 2 行 第一行 1 个整数,n,表示一元多项式的次数. ...
- 洛谷P3372 【模板】线段树 1
P3372 [模板]线段树 1 153通过 525提交 题目提供者HansBug 标签 难度普及+/提高 提交 讨论 题解 最新讨论 [模板]线段树1(AAAAAAAAA- [模板]线段树1 洛谷 ...
- 洛谷P4891 序列(势能线段树)
洛谷题目传送门 闲话 考场上一眼看出这是个毒瘤线段树准备杠题,发现实在太难调了,被各路神犇虐哭qwq 考后看到各种优雅的暴力AC......宝宝心里苦qwq 思路分析 题面里面是一堆乱七八糟的限制和性 ...
随机推荐
- 机器学习:KNN
KNN:K-nearst neighbors 简介: k-近邻算法采用测量不同特征值之间的距离来进行分类,简而言之为:人以类聚,物以群分 KNN既可以应用于分类中,也可用于回归中:在分类的预测是,一般 ...
- Java版流媒体编解码和图像处理(JavaCPP+FFmpeg)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- $dy$讲课总结
字符串: 1.广义后缀自动机(大小为\(m\))上跑一个长度为\(n\)的串,所有匹配位置及在\(parent\)树上其祖先的数量的和为\(min(n^2,m)\),单次最劣是\(O(m)\). 但是 ...
- 你一定不知道的Unsafe用法
Unsafe是什么 首先我们说Unsafe类位于rt.jar里面sun.misc包下面,Unsafe翻译过来是不安全的,这倒不是说这个类是不安全的,而是说开发人员使用Unsafe是不安全的,也就是不推 ...
- 小白自制Linux开发板 十. NES游戏玩起来
本篇基于我们制作的Debian文件系统而展开,而且我们这会玩一些高级的操作方式--用我们的小电脑进行程序编译. 所以本篇操作全部都在我们个的开发板上完成. 1. 开发环境搭建 首先安装gcc, ...
- jQuery中onload与ready区别
onload和ready的区别document.ready和onload的区别为:加载程度不同.执行次数不同.执行速度不同.1.加载程度不同 document.ready:是DOM结构绘制完毕后就执行 ...
- Matlab画colormap的一种色彩搭配方法
声学与振动数据分析经常需要画colormap,来识别是结构频率共振,还是激励源阶次问题,比如图1,横坐标表示电机的转速,负值表示CW(顺时针)方向转动,正值表示CCW逆时针方向转动.Y轴表示对应的声音 ...
- 2016-12-01,我的CSDN有排名啦!
等了好久终于等到今天,梦了好久终于把梦实现----[捂脸] 从2015-08-03发表第一篇博客以来,这里就成了我记录技术成长点滴,学习过程,总结备忘的地方,虽然中间自己也在腾讯云上搭过自己的博客,但 ...
- 装了这几个IDEA插件,基本上一站式开发了!
前言 前几天有社区小伙伴私聊我,问我都用哪些IDEA插件,我的IDEA的主题看起来不错. 作为一个开源作者,每周要code大量的代码,提升日常工作效率是我一直追求的,在众多的IDEA插件中,我独钟爱这 ...
- jenkins持续集成Allure生成报表+邮件推送
本次基于<jenkins 生成HTML报表,邮件推送>的基础上将生成HTML报表修改为Allure生成报表,可以参考官方文档:https://docs.qameta.io/allure/# ...