洛谷 P3688 - [ZJOI2017]树状数组(二维线段树+标记永久化)
首先学过树状数组的应该都知道,将树状数组方向写反等价于前缀和 \(\to\) 后缀和,因此题目中伪代码的区间求和实质上是 \(sum[l-1...n]-sum[r...n]=sum[l-1...r-1]\),我们要求 \(sum[l...r]=sum[l-1...r-1]\) 的概率,等价于求 \(a_{l-1}=a_r\) 的概率。
因此我们可将题目转化为,每次从 \([l,r]\) 中随机选择一个数将其状态翻转,并询问 \(a_x=a_y\) 的概率。
这个可以通过二维线段树解决。建一棵二维线段树,第 \(x\) 行第 \(y\) 个位置上的值 \(p_{x,y}\) 表示 \(a_x=a_y\) 的概率。考虑一次修改 \([l,r]\) 对 \(p_{i,j}\) 的影响,分三种情况:
- \(i\in [1,l-1],j\in[l,r]\),\(a_i\) 不会发生变化,\(a_j\) 有 \(p_1=\dfrac{1}{r-l+1}\) 的概率发生变化,故 \(p_{i,j}=(1-p_{i,j})\times p_1+p_{i,j}\times(1-p_1)\)。
- \(i\in [l,r],j\in[l,r]\),\(a_i,a_j\) 各有 \(\dfrac{1}{r-l+1}\) 的概率发生变化,发生变化的总概率 \(p_2=\dfrac{2}{r-l+1}\),故 \(p_{i,j}=(1-p_{i,j})\times p_2+p_{i,j}\times(1-p_2)\)。
- \(i\in [l,r],j\in[r+1,n]\),\(a_j\) 不会发生变化,\(a_i\) 有 \(p_3=\dfrac{1}{r-l+1}\) 的概率发生变化,故 \(p_{i,j}=(1-p_{i,j})\times p_3+p_{i,j}\times(1-p_3)\)。
对于这三种情况,相当于是对二维线段树上一个矩形执行 \(p_{i,j}\leftarrow (1-p_{i,j})\times P+p_{i,j}\times (1-P)\),这就直接在内层线段树上的区间上打一个 \(P\) 的标记。当合并两个标记 \(P,Q\) 时候,令新的标记 \(R=(1-P)\times Q+(1-Q)\times P\)。由于标记不能下放,因此需要标记永久化,时间复杂度线性二次对数。
当然你可能会有疑惑,上面三种情况中没有考虑 \(i\in [r+1,n],j\in[l,r]\) 的情况,当 \(i=j\) 时候 \(p_{i,j}\) 发生的变化的概率应当为 \(0\),也不是所谓的 \(\dfrac{2}{r-l+1}\),为什么算出来的概率还是正确的呢?事实上,我们查询的时候一定有 \(l-1<r\),因此我们只需维护 \(i<j\) 的 \(p_{i,j}\) 的值即可, 上述写法只不过更方便我们执行二维线段树上的矩形加罢了,不会对算法正确性产生影响。
最后特判 \(l=1\) 的情况,当执行的操作次数为奇数的时候,\(sum[l-1...n]\) 的真实值应当为 \(1\),而伪代码为了确保不卡入死循环直接返回了 \(0\),因此若执行的操作次数为奇数,我们要求的实质上是 \(a_{l-1}\ne a_r\) 的概率。
#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 MOD=998244353;
int qpow(int x,int e){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
return ret;
}
int n,qu,ncnt=0;
struct node{int ch[2],tag;} s[MAXN*500+5];
int rt[MAXN*4+5];
void modify_in(int &k,int l,int r,int ql,int qr,int p){
if(!k){k=++ncnt;s[k].tag=1;}
if(ql<=l&&r<=qr){
s[k].tag=(1ll*p*s[k].tag+1ll*(1+MOD-p)*(1+MOD-s[k].tag))%MOD;
return;
} int mid=l+r>>1;
if(qr<=mid) modify_in(s[k].ch[0],l,mid,ql,qr,p);
else if(ql>mid) modify_in(s[k].ch[1],mid+1,r,ql,qr,p);
else modify_in(s[k].ch[0],l,mid,ql,mid,p),modify_in(s[k].ch[1],mid+1,r,mid+1,qr,p);
}
void modify_out(int k,int l,int r,int ql,int qr,int qx,int qy,int p){
if(ql<=l&&r<=qr){modify_in(rt[k],1,n,qx,qy,p);return;}
int mid=l+r>>1;
if(qr<=mid) modify_out(k<<1,l,mid,ql,qr,qx,qy,p);
else if(ql>mid) modify_out(k<<1|1,mid+1,r,ql,qr,qx,qy,p);
else modify_out(k<<1,l,mid,ql,mid,qx,qy,p),modify_out(k<<1|1,mid+1,r,mid+1,qr,qx,qy,p);
}
int query_in(int k,int l,int r,int p){
if(!k) return 1;if(l==r) return s[k].tag;
int mid=l+r>>1,pp=(p<=mid)?query_in(s[k].ch[0],l,mid,p):query_in(s[k].ch[1],mid+1,r,p);
return (1ll*pp*s[k].tag+1ll*(MOD+1-pp)*(MOD+1-s[k].tag))%MOD;
}
int query_out(int k,int l,int r,int p,int q){
if(l==r) return query_in(rt[k],1,n,q);
int mid=l+r>>1;
int p1=(p<=mid)?query_out(k<<1,l,mid,p,q):query_out(k<<1|1,mid+1,r,p,q);
int p2=query_in(rt[k],1,n,q);
return (1ll*p1*p2+1ll*(MOD+1-p1)*(MOD+1-p2))%MOD;
}
int main(){
scanf("%d%d",&n,&qu);int cnt=0;
while(qu--){
int opt,l,r;scanf("%d%d%d",&opt,&l,&r);
if(opt==1){
int p=qpow(r-l+1,MOD-2);cnt^=1;
modify_out(1,0,n,0,l-1,l,r,(MOD+1-p)%MOD);
if(r^n) modify_out(1,0,n,l,r,r+1,n,(MOD+1-p)%MOD);
modify_out(1,0,n,l,r,l,r,(MOD+1-2*p%MOD)%MOD);
} else {
int ret=query_out(1,0,n,l-1,r);
if(!(l^1)&&cnt) ret=(MOD+1-ret)%MOD;
printf("%d\n",ret);
}
}
return 0;
}
洛谷 P3688 - [ZJOI2017]树状数组(二维线段树+标记永久化)的更多相关文章
- bzoj4785:[ZJOI2017]树状数组:二维线段树
分析: "如果你对树状数组比较熟悉,不难发现可怜求的是后缀和" 设数列为\(A\),那么可怜求的就是\(A_{l-1}\)到\(A_{r-1}\)的和(即\(l-1\)的后缀减\( ...
- BZOJ 4785 [Zjoi2017]树状数组 | 二维线段树
题目链接 BZOJ 4785 题解 这道题真是令人头秃 = = 可以看出题面中的九条可怜把求前缀和写成了求后缀和,然后他求的区间和却仍然是sum[r] ^ sum[l - 1],实际上求的是闭区间[l ...
- 树状数组 二维偏序【洛谷P3431】 [POI2005]AUT-The Bus
P3431 [POI2005]AUT-The Bus Byte City 的街道形成了一个标准的棋盘网络 – 他们要么是北南走向要么就是西东走向. 北南走向的路口从 1 到 n编号, 西东走向的路从1 ...
- 洛谷 P1972 [SDOI2009]HH的项链-二维偏序+树状数组+读入挂(离线处理,思维,直接1~n一边插入一边查询),hahahahahahaha~
P1972 [SDOI2009]HH的项链 题目背景 无 题目描述 HH 有一串由各种漂亮的贝壳组成的项链.HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含 ...
- BZOJ4822[Cqoi2017]老C的任务——树状数组(二维数点)
题目描述 老 C 是个程序员. 最近老 C 从老板那里接到了一个任务——给城市中的手机基站写个管理系统.作为经验丰富的程序员,老 C 轻松 地完成了系统的大部分功能,并把其中一个功能交给你来实 ...
- BZOJ1935: [Shoi2007]Tree 园丁的烦恼(树状数组 二维数点)
题意 题目链接 Sol 二维数点板子题 首先把询问拆成四个矩形 然后离散化+树状数组统计就可以了 // luogu-judger-enable-o2 #include<bits/stdc++.h ...
- 树状数组+二维前缀和(A.The beautiful values of the palace)--The Preliminary Contest for ICPC Asia Nanjing 2019
题意: 给你螺旋型的矩阵,告诉你那几个点有值,问你某一个矩阵区间的和是多少. 思路: 以后记住:二维前缀和sort+树状数组就行了!!!. #define IOS ios_base::sync_wit ...
- bzoj 4822: [Cqoi2017]老C的任务【扫描线+树状数组+二维差分】
一个树状数组能解决的问题分要用树套树--还写错了我别是个傻子吧? 这种题还是挺多的,大概就是把每个矩形询问差分拆成四个点前缀和相加的形式(x1-1,y1-1,1)(x2.y2,1)(x1-1,y2,- ...
- 【BZOJ3110】【整体二分+树状数组区间修改/线段树】K大数查询
Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c 如果是2 a b c形式,表示询问从第a个位置到第b个位 ...
- [Usaco2014 Open Gold ]Cow Optics (树状数组+扫描线/函数式线段树)
这道题一上手就知道怎么做了= = 直接求出原光路和从目标点出发的光路,求这些光路的交点就行了 然后用树状数组+扫描线或函数式线段树就能过了= = 大量的离散+模拟+二分什么的特别恶心,考试的时候是想到 ...
随机推荐
- 在kivy中加图片
from kivy.app import App from kivy.uix.scatterlayout import ScatterLayout from kivy.uix.image import ...
- [no code][scrum meeting] Beta 5
$( "#cnblogs_post_body" ).catalog() 例会时间:5月18日14:30,主持者:叶开辉 下次例会时间:5月19日11:30,主持者:黎正宇 一.工作 ...
- 【二食堂】Alpha - 测试报告
TextMarking Alpha阶段测试报告 前后端测试过程及结果 在Alpha阶段,测试工作紧跟后端开发进度,一下是我们所做的一些测试工作. 后端单元测试 测试代码可以在git仓库中查看,后端对所 ...
- 文献翻译|Design of True Random Number Generator Based on Multi-stage Feedback Ring Oscillator(基于多级反馈环形振荡器的真随机数发生器设计)
基于多级反馈环形振荡器的真随机数发生器设计 摘要 真随机数生成器(trng)在加密系统中起着重要的作用.本文提出了一种在现场可编程门阵列(FPGA)上生成真随机数的新方法,该方法以 多级反馈环形振荡器 ...
- 同人逼死官方系列!从 DDC 嗅探器到 sddc_sdk_lib 的数据解析
从 DDC 嗅探器到 sddc_sdk_lib 的数据解析 之前的 DDC 协议介绍 主要讲了设备加入.退出以及维持设备状态,而 SDK框架 sddc_sdk_lib 解析 主要讲了 SDK 库的结构 ...
- vsftpd 编译安装 及 隐藏版本号
环境:Redhat Enterprise Linux AS 4.0 update2(i386) 不提示,均表示以root权限执行. [注:]//为注释符,如"// 建立MySQL组" ...
- testNG 注解使用说明
1.TestNG常用注解 @BeforeSuite 标记的方法:在某个测试套件(suite)开始之前运行 @BeforeTest 在某个测试(test)开始之前运行 @BeforeClass 在某个测 ...
- Qt 使用大神插件快速创建树状导航栏
前言 本博客仅仅记录自己的采坑过程以及帮助网友避坑,方便以后快速使用自定义控件,避免重复出错. 下载插件 大神 Github Qt 自定义控件项目地址:https://github.com/feiya ...
- Linux 服务器的基本性能及测试方法
1. 摘要 一个基于 Linux 操作系统的服务器运行的同时,也会表征出各种各样参数信息.通常来说运维人员.系统管理员会对这些数据会极为敏感,但是这些参数对于开发者来说也十分重要,尤其当程序非正常工作 ...
- 菜鸡的Java笔记 第二十六 - java 内部类
/* innerClass 从实际的开发来看,真正写到内部类的时候是在很久以后了,短期内如果是自己编写代码,几乎是见不到内部类出现的 讲解它的目的第一个是为了解释概念 ...