题面传送门

神仙 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,神仙题)的更多相关文章

  1. 洛谷P5280 [ZJOI2019]线段树

      https://www.luogu.org/problemnew/show/P5280 省选的时候后一半时间开这题,想了接近两个小时的各种假做法,之后想的做法已经接近正解了,但是有一些细节问题理不 ...

  2. 洛谷P5280 [ZJOI2019]线段树 [线段树,DP]

    传送门 无限Orz \(\color{black}S\color{red}{ooke}\)-- 思路 显然我们不能按照题意来每次复制一遍,而多半是在一棵线段树上瞎搞. 然后我们可以从\(modify\ ...

  3. 洛谷P5280 [ZJOI2019]线段树(线段树)

    题面 传送门 题解 考场上就这么一道会做的其它连暴力都没打--活该爆炸-- 首先我们得看出问题的本质:有\(m\)个操作,总共\(2^m\)种情况分别对应每个操作是否执行,求这\(2^m\)棵线段树上 ...

  4. 洛谷 P2622 关灯问题II(状压DP入门题)

    传送门 https://www.cnblogs.com/violet-acmer/p/9852294.html 题解: 相关变量解释: int n,m; ];//a[i][j] : 第i个开关对第j个 ...

  5. 洛谷 P3373 【模板】线段树 2

    洛谷 P3373 [模板]线段树 2 洛谷传送门 题目描述 如题,已知一个数列,你需要进行下面三种操作: 将某区间每一个数乘上 xx 将某区间每一个数加上 xx 求出某区间每一个数的和 输入格式 第一 ...

  6. 【BZOJ3244】【NOI2013】树的计数(神仙题)

    [BZOJ3244][NOI2013]树的计数(神仙题) 题面 BZOJ 这题有点假,\(bzoj\)上如果要交的话请输出\(ans-0.001,ans,ans+0.001\) 题解 数的形态和编号没 ...

  7. 洛谷P1067 多项式输出 NOIP 2009 普及组 第一题

    洛谷P1067 多项式输出 NOIP 2009 普及组 第一题 题目描述 一元n次多项式可用如下的表达式表示: 输入输出格式 输入格式 输入共有 2 行 第一行 1 个整数,n,表示一元多项式的次数. ...

  8. 洛谷P3372 【模板】线段树 1

    P3372 [模板]线段树 1 153通过 525提交 题目提供者HansBug 标签 难度普及+/提高 提交  讨论  题解 最新讨论 [模板]线段树1(AAAAAAAAA- [模板]线段树1 洛谷 ...

  9. 洛谷P4891 序列(势能线段树)

    洛谷题目传送门 闲话 考场上一眼看出这是个毒瘤线段树准备杠题,发现实在太难调了,被各路神犇虐哭qwq 考后看到各种优雅的暴力AC......宝宝心里苦qwq 思路分析 题面里面是一堆乱七八糟的限制和性 ...

随机推荐

  1. 初学Python-day10 函数2

    函数 1.函数也是一种数据 函数也是一种数据,可以使用变量保存 回调函数(参数的值还是一个函数) 实例: def test(): print('hello world') def test1(a): ...

  2. JavaScript03

    类型转换和运算符 typeof函数 检测数据类型,可以使用以下两种调用的方式: typeof 变量或表达式 typeof(变量或表达式) var n="asda"; console ...

  3. nssm.exe使用方法

    nssm no-sucking service manager 1. 安装服务命令 nssm install <servicename> nssm install <servicen ...

  4. “妈妈再也不用担心我忘交作业了!”——记2020BUAA软工团队项目选择

    写在前面 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任建) 这个作业的要求在哪里 团队项目选择 项目简介 项目名称:北航学生资源整合和作业提醒平台 项目内容: 设计实现一 ...

  5. BUAA_2020_软件工程_提问回顾与总结

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任建) 这个作业的要求在哪里 提问回顾与总结作业要求 我在这个课程的目标 了解软件工程的技术,掌握工程化开发的能力 这个作业在哪 ...

  6. numpy读取本地数据和索引

    1.numpy读取数据 np.loadtxt(fname,dtype=np.float,delimiter=None,skiprows=0,usecols=None,unpack=False) 做一个 ...

  7. 【做题记录】 [JLOI2011]不等式组

    P5482 [JLOI2011]不等式组 超烦人的细节题!(本人调了两天 QAQ ) 这里介绍一种只用到一只树状数组的写法(离线). 树状数组的下标是:所有可能出现的数据进行离散化之后的值. 其含义为 ...

  8. hdu 2191 珍惜现在,感恩生活(多重背包)

    题意: 有N元经费,M种大米,每种大米有单袋价格p元,单袋重量h,以及对应袋数c. 问最多可以买多重的大米. 思路: 经典多重背包,用二进制的方法. 看代码 代码: struct node{ int ...

  9. mysql数据库导入导出文件sql文件

    window下 1.导出整个数据库 mysqldump -u 用户名 -p 数据库名 > 导出的文件名 mysqldump -u dbuser -p dbname > dbname.sql ...

  10. SQL*Loader-704: Internal error: ulconnect: OCIServerAttach [0] ORA-12541: TNS:no listener

    使用/app/oracle/product/11.2.0/bin/sqlldr导入数据报错: 监听没有开启?检查发现监正常 猜测是监听端口不是默认的1521有关系,直接在sid里面加上数据库服务器的i ...