洛谷 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 思路分析 题面里面是一堆乱七八糟的限制和性 ...
随机推荐
- 剑指offer:JZ12 矩阵中的路径
JZ12 矩阵中的路径 描述 请设计一个函数,用来判断在一个n乘m的矩阵中是否存在一条包含某长度为len的字符串所有字符的路径.路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上, ...
- [no code][scrum meeting] Beta 11
$( "#cnblogs_post_body" ).catalog() 例会时间:5月26日11:30,主持者:肖思炀 下次例会时间:5月27日11:30,主持者:乔玺华 一.工作 ...
- STM32中断编程三步曲教你弄会中断设置以及中断优先级设置
中断作为stm32中必不可少的一个功能,其重要性是不言而喻的因此把中断学习好是根本. 所以今天就来好好啃一下中断配置的知识,俗话说:磨刀不误砍柴工.问题是什么呢?项目中我用到了一个触摸键盘TTP229 ...
- 算法:汉诺塔问题(Tower of Brahma puzzle)
一.算法背景 最早发明这个问题的人是法国数学家爱德华·卢卡斯.传说越南河内某间寺院有三根银棒(A, B, C),上串 64 个金盘. 寺院里的僧侣依照一个古老的预言,以上述规则移动这些盘子:预言说当这 ...
- FPGA基础之锁存器与触发器的设计
转载:https://blog.csdn.net/lg2lh/article/details/39081061 一.锁存器 首先设计锁存器的时候应该清楚什么是锁存器,锁存器其实是对电平信号敏感的,一定 ...
- 树形DP 枚举祖宗的例题
这类题目是真的很头疼....其实这类题目的特征也很明显,叶子结点贡献答案时和其所在链的祖宗有关,也就是说要想得知其贡献必须知道他的所有祖宗的贡献,其实处理方法也不是太难,就是在dfs枚举时顺便把祖宗的 ...
- hdu 2830 Matrix Swapping II(额,,排序?)
题意: N*M的矩阵,每个格中不是0就是1. 可以任意交换某两列.最后得到一个新矩阵. 问可以得到的最大的子矩形面积是多少(这个子矩形必须全是1). 思路: 先统计,a[i][j]记录从第i行第j列格 ...
- MarkDown学习随笔
MarkDown语法的学习 标题 设置标题方法是在前面加#号,一级标题(最大)是加#+空格 ,二级标题是加##+空格,之后的以此类推. 字体 在文本的前后分别加上一个星号表示斜体字 在文本的前后分 ...
- OpenHarmony LiteOS C-SKY指令集移植指北
摘要:本文介绍在OpenHarmony社区LiteOS-M项目中新增C-SKY指令集的开发流程,以及适配相应qemu工程的方法和步骤,供LiteOS内核相关开发者学习交流. 本文分享自华为云社区< ...
- linux下go环境配置
环境申明: centos 7.4 1.go下载最新的版本(linux) 下载本地后rz到服务器,然后tar -zxvf go1.9.2.linux-amd64.tar.gz 解压出go文件放在 ...