题面传送门

首先学过树状数组的应该都知道,将树状数组方向写反等价于前缀和 \(\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. linux中文件查找、whereis、which、输出命令

    1.文件查找(find):find是最常⻅和最强⼤的查找命令 格式:find / -name  文件名,比如:find / -name mysql.  (1).模糊查找:*是代表所有的,?是代表⼀个字 ...

  2. SpringCloud微服务实战——搭建企业级开发框架(四):集成SpringCloud+SpringBoot

    1.在GitEgg工程的根目录,最上级父pom.xml文件中引入需要依赖的库及Maven插件,设置编码方式: <!--?xml version="1.0" encoding= ...

  3. [Git系列] Git 基本概念

    版本控制系统 版本控制系统是一种帮助软件开发者实现团队合作和历史版本维护的软件,一个版本控制系统应具备以下列出的这几个基本功能: 允许开发者并发工作: 不允许一个开发者覆写另一个开发者的修改: 保存所 ...

  4. HttpClient.PatchAsJsonAsync - dotnet/runtime 项目贡献小记

    TL;DR 迫于 PatchAsJsonAsync 方法缺失,我给 dotnet/runtime 项目贡献了相关的 API,可惜要到 .NET7 才能用上. https://github.com/do ...

  5. oo第四次博客-UML暨学期总结

    一. 本单元两次作业架构设计 这两次作业实际上难度不大,不存在算法上的难题,大部分时间都是用在处理UML图中各个元素的关系上. 第一次UML主要处理UML类图.有UMLclass,UMLinterfa ...

  6. Noip模拟7 2021.6.11

    前言 考试时候der展了,T1kmp没特判(看来以后还是能hash就hash),T2搜索细节没注意,ans没清零,130飞到14.... T1 匹配(hash/kmp) 这太水了,其实用个hash随便 ...

  7. 查找最小生成树:普里姆算法算法(Prim)算法

    一.算法介绍 普里姆算法(Prim's algorithm),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之 ...

  8. Ubuntu 16.04 菜单栏 换位置 挪到左边 挪到下边

    Ubuntu菜单栏的位置可以调 到左侧 或者底部 调整到底部 $ gsettings set com.canonical.Unity.Launcher launcher-position Bottom ...

  9. 【数据结构&算法】02-复杂度分析之执行效率和资源消耗

    目录 前言 复杂度 分析方法 大 O 复杂度表示法 例子-评估累加和的各种算法执行效率 算法 1(for 循环): 算法 2(嵌套 for 循环): 大 O 表示 时间复杂度分析 关注执行最多的一段代 ...

  10. Canal 实战 | 第一篇:SpringBoot 整合 Canal + RabbitMQ 实现监听 MySQL 数据库同步更新 Redis 缓存

    一. Canal 简介 canal [kə'næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费 早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同 ...