题面传送门

首先学过树状数组的应该都知道,将树状数组方向写反等价于前缀和 \(\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]树状数组(二维线段树+标记永久化)的更多相关文章

  1. bzoj4785:[ZJOI2017]树状数组:二维线段树

    分析: "如果你对树状数组比较熟悉,不难发现可怜求的是后缀和" 设数列为\(A\),那么可怜求的就是\(A_{l-1}\)到\(A_{r-1}\)的和(即\(l-1\)的后缀减\( ...

  2. BZOJ 4785 [Zjoi2017]树状数组 | 二维线段树

    题目链接 BZOJ 4785 题解 这道题真是令人头秃 = = 可以看出题面中的九条可怜把求前缀和写成了求后缀和,然后他求的区间和却仍然是sum[r] ^ sum[l - 1],实际上求的是闭区间[l ...

  3. 树状数组 二维偏序【洛谷P3431】 [POI2005]AUT-The Bus

    P3431 [POI2005]AUT-The Bus Byte City 的街道形成了一个标准的棋盘网络 – 他们要么是北南走向要么就是西东走向. 北南走向的路口从 1 到 n编号, 西东走向的路从1 ...

  4. 洛谷 P1972 [SDOI2009]HH的项链-二维偏序+树状数组+读入挂(离线处理,思维,直接1~n一边插入一边查询),hahahahahahaha~

    P1972 [SDOI2009]HH的项链 题目背景 无 题目描述 HH 有一串由各种漂亮的贝壳组成的项链.HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含 ...

  5. BZOJ4822[Cqoi2017]老C的任务——树状数组(二维数点)

    题目描述 老 C 是个程序员.     最近老 C 从老板那里接到了一个任务——给城市中的手机基站写个管理系统.作为经验丰富的程序员,老 C 轻松 地完成了系统的大部分功能,并把其中一个功能交给你来实 ...

  6. BZOJ1935: [Shoi2007]Tree 园丁的烦恼(树状数组 二维数点)

    题意 题目链接 Sol 二维数点板子题 首先把询问拆成四个矩形 然后离散化+树状数组统计就可以了 // luogu-judger-enable-o2 #include<bits/stdc++.h ...

  7. 树状数组+二维前缀和(A.The beautiful values of the palace)--The Preliminary Contest for ICPC Asia Nanjing 2019

    题意: 给你螺旋型的矩阵,告诉你那几个点有值,问你某一个矩阵区间的和是多少. 思路: 以后记住:二维前缀和sort+树状数组就行了!!!. #define IOS ios_base::sync_wit ...

  8. bzoj 4822: [Cqoi2017]老C的任务【扫描线+树状数组+二维差分】

    一个树状数组能解决的问题分要用树套树--还写错了我别是个傻子吧? 这种题还是挺多的,大概就是把每个矩形询问差分拆成四个点前缀和相加的形式(x1-1,y1-1,1)(x2.y2,1)(x1-1,y2,- ...

  9. 【BZOJ3110】【整体二分+树状数组区间修改/线段树】K大数查询

    Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c 如果是2 a b c形式,表示询问从第a个位置到第b个位 ...

  10. [Usaco2014 Open Gold ]Cow Optics (树状数组+扫描线/函数式线段树)

    这道题一上手就知道怎么做了= = 直接求出原光路和从目标点出发的光路,求这些光路的交点就行了 然后用树状数组+扫描线或函数式线段树就能过了= = 大量的离散+模拟+二分什么的特别恶心,考试的时候是想到 ...

随机推荐

  1. Spark解决SQL和RDDjoin结果不一致问题(工作实录)

    问题描述:DataFrame的join结果不正确,dataframeA(6000无重复条数据) join dataframeB(220条无重复数据,由dataframeA转化而来,key值均源于dat ...

  2. C#特性知识图谱-二、事件

    C#特性知识图谱-二.事件 二.事件 在事件驱动的软件系统中,符合某种预设条件的情形出现是,一个事件就会被触发. 2.1 事件三要素 事件源:激发事件的对象 事件信息:事件本身说携带的信息 事件响应者 ...

  3. Golang通脉之面向对象

    面向对象的三大特征: 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式 继承:使得子类具有父类的属性和方法或者重新定义.追加属性和方法等 多态:不同对象中同种行为的不同实现方式 Go并不是一个纯 ...

  4. 【Takin使用日记】记一次TransmittableThreadLocal引起的业务异常

    对于常见的 WEB 容器,Takin 通过增强 org.apache.catalina.core.StandardHostValve#invoke 方法,拦截并解析方法入参的 Request 对象中的 ...

  5. P2120 [ZJOI2007]仓库建设

    P2120 [ZJOI2007]仓库建设 怎么说呢?算是很水的题了吧... 只要不要一开始就把dp想错就行... #include<bits/stdc++.h> #define ll lo ...

  6. newusers 拷贝服务器A上的用户,批量添加到其它服务器

    服务器B 需要添加多个用户,要求与服务器A 的用户列表一致 1.拷贝服务器A 上的 /etc/passwd 中用户信息,用user1-10为例 #grep ^user /etc/passwd > ...

  7. Typecho部署小破站

    写在前面 以前利用 Github Page + Hexo框架 + Next主题搭建过静态博客,没错就是那个黑白色系的网页!但是体验并不是很好,一来本身是静态网页,页面内容要修改都需要在本地修改完上传到 ...

  8. Linux&C网络编程————“聊天室”

    从上周到现在一直在完成最后的项目,自己的聊天室,所以博客就没怎么跟了,今天晚上自己的聊天室基本实现,让学长检查了,也有好些bug,自己还算满意,主要实现的功能有: 登录注册 附近的人(服务器端全部在线 ...

  9. let that = this用法解析

    这种情况就是在一个代码片段里this有可能代表不同的对象,而编码者希望this代表最初的对象

  10. 学信网改绑手机号码,但是忘记了老号码怎么办?利用node.js + puppeteer 跑脚本实现改绑手机号

    最近登录学信网发现自己学信网上绑定的手机号码不是目前自己使用的手机号码,于是想改绑手机号,但是发现不记得之前的手机号码了: 于是百度各种方法都无济于事:也不想重新注册账号,最后看见一篇文章通过Pyth ...