【bzoj4785】[Zjoi2017]树状数组 线段树套线段树

题目描述

漆黑的晚上,九条可怜躺在床上辗转反侧。难以入眠的她想起了若干年前她的一次悲惨的OI 比赛经历。那是一道基础的树状数组题。给出一个长度为 n 的数组 A,初始值都为 0,接下来进行 m 次操作,操作有两种:

1 x,表示将 Ax 变成 (Ax + 1) mod 2。

2 l r,表示询问 sigma(Ai) mod 2,L<=i<=r

尽管那个时候的可怜非常的 simple,但是她还是发现这题可以用树状数组做。当时非常young 的她写了如下的算法:

1: function Add(x)

2: while x > 0 do

3: A

x ← (Ax + 1) mod 2

4: x ← x - lowbit(x)

5: end while

6: end function

7:

8: function Find(x)

9: if x == 0 then

10: return 0

11: end if

12: ans ← 0

13: while x ≤ n do

14: ans ← (ans + Ax) mod 2

15: x ← x + lowbit(x)

16: end while

17: return ans

18: end function

19:

20: function Query(l, r)

21: ansl ← Find(l - 1)

22: ansr ← Find(r)

23: return (ansr ? ansl + 2) mod 2

24: end function

其中 lowbit(x) 表示数字 x 最高的非 0 二进制位,例如 lowbit(5) = 1, lowbit(12) = 4。进行第一类操作的时候就调用 Add(x),第二类操作的时候答案就是 Query(l, r)。如果你对树状数组比较熟悉,不难发现可怜把树状数组写错了: Add和Find 中 x 变化的方向反了。因此这个程序在最终测试时华丽的爆 0 了。然而奇怪的是,在当时,这个程序通过了出题人给出的大样例——这也是可怜没有进行对拍的原因。现在,可怜想要算一下,这个程序回答对每一个询问的概率是多少,这样她就可以再次的感受到自己是一个多么非的人了。然而时间已经过去了很多年,即使是可怜也没有办法完全回忆起当时的大样例。幸运的是,她回忆起了大部分内容,唯一遗忘的是每一次第一类操作的 x的值,因此她假定这次操作的 x 是在 [li, ri] 范围内 等概率随机 的。具体来说,可怜给出了一个长度为 n 的数组 A,初始为 0,接下来进行了 m 次操作:

1 l r,表示在区间 [l, r] 中等概率选取一个 x 并执行 Add(x)。

2 l r,表示询问执行 Query(l, r) 得到的结果是正确的概率是多少。

输入

第一行输入两个整数 n, m。

接下来 m 行每行描述一个操作,格式如题目中所示。

N<=10^5,m<=10^5,1<=L<=R<=N

输出

对于每组询问,输出一个整数表示答案。如果答案化为最简分数后形如 x/y,那么你只需要输出 x*y^?1 mod 998244353 后的值。(即输出答案模 998244353)。

样例输入

5 5
1 3 3
2 3 5
2 4 5
1 1 3
2 2 5

样例输出

1
0
665496236



题解

线段树套线段树

“如果你对树状数组比较熟悉,不难发现”本题中树状数组求的是后缀和。

那么当$l-1\neq 0$时(等于0时再单独讨论),求出的结果即为$\sum\limits_{i=l-1}^{r-1}A_i$,若与$\sum\limits_{i=l}^rA_i$相等,则要求$A_{l-1}=A_r$。所以只需要求出$A_{l-1}=A_r$的概率即可。

我们想,对于修改操作[l,r],如果已经确定了左端点t和右端点k,如何更新t与k(k>t)相等的概率呢?

肯定是要分情况讨论,当然其中只有当$t$或$k\in[l,r]$时才会产生影响。

1.当$t\in[1,l-1]$,$k\in[l,r]$时,不影响的概率为1-p

2.当$t\in[l,r]$,$k\in[l,r]$时,不影响的概率为1-2p

3.当$t\in[l,r]$,$k\in[r+1,n]$时,不影响的概率为1-p。

如果确定了t,我们显然可以使用线段树维护这三段区间。至于概率的问题,如果原来相等的概率为p,不影响的概率为q,那么新的相等的概率显然为$p·q-(1-p)(1-q)$。并且这个式子满足交换律和结合律,因此更新顺序是不需要考虑的(并且可以标记永久化)。

而由于t的存在情况也是连续的区间,所以我们还需要一颗线段树维护左端点t,所以需要线段树套线段树,即二维线段树。

具体实现:使用类似于标记永久化的思想,选择一段外层区间和内层区间,就把(外层区间对应的外层节点)对应的(内层区间对应的内层节点)更新。

至于查询[l,r],则查找(外层线段树中l-1对应的节点)对应的(内层线段树中r对应的节点)。因为永久化了标记,所以所有经过的节点对答案的贡献都需要记录到答案中(特别是外层线段树)。

以上就是$l\neq 1$的情况,至于l=1的情况,同理,要保证的是r的前缀和等于后缀和,采用同样的思路维护一下就好了,具体见代码中对外层线段树0节点的操作。

代码真心不长~

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
using namespace std;
typedef long long ll;
const ll mod = 998244353;
int root[N << 2] , ls[N << 8] , rs[N << 8] , tot , n;
ll sum[N << 8];
ll cal(ll x , ll y)
{
	return (x * y + (1 - x + mod) * (1 - y + mod)) % mod;
}
ll pow(ll x , ll y)
{
	ll ans = 1;
	while(y)
	{
		if(y & 1) ans = ans * x % mod;
		x = x * x % mod , y >>= 1;
	}
	return ans;
}
void update(int b , int e , ll v , int l , int r , int &x)
{
	if(!x) x = ++tot , sum[x] = 1;
	if(b <= l && r <= e)
	{
		sum[x] = cal(sum[x] , v);
		return;
	}
	int mid = (l + r) >> 1;
	if(b <= mid) update(b , e , v , l , mid , ls[x]);
	if(e > mid) update(b , e , v , mid + 1 , r , rs[x]);
}
ll query(int p , int l , int r , int x)
{
	if(!x) return 1;
	if(l == r) return sum[x];
	int mid = (l + r) >> 1;
	if(p <= mid) return cal(sum[x] , query(p , l , mid , ls[x]));
	else return cal(sum[x] , query(p , mid + 1 , r , rs[x]));
}
void modify(int p , int q , ll v , int b , int e , int l , int r , int x)
{
	if(p <= l && r <= q)
	{
		update(b , e , v , 1 , n , root[x]);
		return;
	}
	int mid = (l + r) >> 1;
	if(p <= mid) modify(p , q , v , b , e , l , mid , x << 1);
	if(q > mid) modify(p , q , v , b , e , mid + 1 , r , x << 1 | 1);
}
ll solve(int p , int q , int l , int r , int x)
{
	if(l == r) return query(q , 1 , n , root[x]);
	int mid = (l + r) >> 1;
	if(p <= mid) return cal(query(q , 1 , n , root[x]) , solve(p , q , l , mid , x << 1));
	else return cal(query(q , 1 , n , root[x]) , solve(p , q , mid + 1 , r , x << 1 | 1));
}
int main()
{
	int m , opt , l , r;
	ll p;
	scanf("%d%d" , &n , &m);
	while(m -- )
	{
		scanf("%d%d%d" , &opt , &l , &r);
		if(opt == 1)
		{
			p = pow(r - l + 1 , mod - 2);
			if(l > 1) modify(1 , l - 1 , (1 - p + mod) % mod , l , r , 0 , n , 1) , modify(0 , 0 , 0 , 1 , l - 1 , 0 , n , 1);
			if(r < n) modify(l , r , (1 - p + mod) % mod , r + 1 , n , 0 , n , 1) , modify(0 , 0 , 0 , r + 1 , n , 0 , n , 1);
			modify(l , r , (1 - (p << 1) % mod + mod) % mod , l , r , 0 , n , 1) , modify(0 , 0 , p , l , r , 0 , n , 1);
		}
		else printf("%lld\n" , solve(l - 1 , r , 0 , n , 1));
	}
	return 0;
}
时间: 2024-10-19 12:28:27

【bzoj4785】[Zjoi2017]树状数组 线段树套线段树的相关文章

POJ2352 Stars 【树状数组】or【线段树】

Stars Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 31172   Accepted: 13595 Description Astronomers often examine star maps where stars are represented by points on a plane and each star has Cartesian coordinates. Let the level of a st

[BZOJ4785][ZJOI2017]树状数组(概率+二维线段树)

4785: [Zjoi2017]树状数组 Time Limit: 40 Sec  Memory Limit: 512 MBSubmit: 297  Solved: 195[Submit][Status][Discuss] Description 漆黑的晚上,九条可怜躺在床上辗转反侧.难以入眠的她想起了若干年前她的一次悲惨的OI 比赛经历.那是一道 基础的树状数组题.给出一个长度为 n 的数组 A,初始值都为 0,接下来进行 m 次操作,操作有两种: 1 x,表示将 Ax 变成 (Ax + 1)

LightOJ 1085(树状数组+离散化+DP,线段树)

All Possible Increasing Subsequences Time Limit:3000MS     Memory Limit:65536KB     64bit IO Format:%lld & %llu Appoint description: Description An increasing subsequence from a sequence A1, A2 ... An is defined by Ai1, Ai2 ... Aik, where the followi

浅析树状数组(二叉索引树)及一些模板

树状数组 动态连续和查询问题.给定一个n个元素的数组a1.a2.……,an,设计一个数据结构,支持以下两种操作:1.add(x,d):让ax增加d;2.query(l,r):计算al+al+1+…+ar 如何让query和add都能快速完成呢?方法有很多,这里介绍的便是树状数组.为此我们先介绍lowbit. 对于正整数x,我们定义lowbit(x)为x的二进制表达式中最右边的1所对应的值(而不是这个比特的序号).比如,38288的二进制1001010110010000,所以lowbit(3828

【数据结构之树状数组】从零认识树状数组

一.关于树状数组 树状数组(Binary Indexed Tree,简称BIT),是一种修改和查询复杂度都为O(logN)的数据结构.但树状数组仅支持单点修改,在查询时,树状数组也要求被查询的区间具有可区间加减的性质.不过,树状数组由于代码实现容易.占用空间小,常用于代替线段树. 二.详解树状数组 在这里,我们定义原序列为a,树状数组为c,则有: 其中,k为i的二进制表示中末尾0的个数,例如:i=3(101)时,k=0:i=8(1000)时,k=3. 定义函数lowbit(x)=2k(k为x的二

实用数据结构---树状数组(二叉索引树)

树状数组适用于动态连续和查询问题,就是给定一个区间, 查询某一段的和或者修改某一位置的值. 关于树状数组的结构请去百度百科,否则将看不懂下面内容 我们看这个题 士兵杀敌(二) 时间限制:1000 ms  |  内存限制:65535 KB 难度:5 描述 南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的. 小工是南将军手下的军师,南将军经常想知道第m号到第n号士兵的总杀敌数,请你帮助小工来回答南将军吧. 南将军的某次询问之后士兵i可能又杀敌q人,之后南将军再询问的时候,需要考虑到新

用树状数组处理逆序对[数据结构][树状数组]

逆序对 ——!x^n+y^n=z^n 可以到这里[luogu]: https://www.luogu.org/problem/show?pid=1908 题意:对于给定的一段正整数序列,逆序对就是序列中ai>aj且i<j的有序对.知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目. 假如为这些数为: 8 2 3 1 7 如果我们把数一个个加进来,用一个数组a[i]统计i出现了几次. a的初始状态: 8加进来后: 由于不存在比8大的数,说明没有产生逆序对 2加进来后: 统计比2大

树状数组(二叉索引树 BIT Fenwick树) *【一维基础模板】(查询区间和+修改更新)

刘汝佳:<训练指南>Page(194) #include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> using namespace std; //一维树状数组基础模板 int lowbit(int x) { return x&(-x); } int c[1001]; int sum(int x) //计算从1到x的数组元素的和 { int

【树状数组(二叉索引树)】轻院热身—candy、NYOJ-116士兵杀敌(二)

[概念] 转载连接:树状数组 讲的挺好. 这两题非常的相似,查询区间的累加和.更新结点.Add(x,d) 与 Query(L,R) 的操作 [题目链接:candy] 唉,也是现在才发现这题用了这个知识,当初A的第一个数据结构的题就是关于树状数组的,忘了忘了.. Problem C: candy Time Limit: 3 Sec  Memory Limit: 128 MBSubmit: 252  Solved: 63 SubmitStatusWeb Board Description Kimi

loj #535. 「LibreOJ Round #6」花火 树状数组求逆序对+主席树二维数点+整体二分

$ \color{#0066ff}{ 题目描述 }$ 「Hanabi, hanabi--」 一听说祭典上没有烟火,Karen 一脸沮丧. 「有的哦-- 虽然比不上大型烟花就是了.」 还好 Shinobu 早有准备,Alice.Ayaya.Karen.Shinobu.Yoko 五人又能继续愉快地玩耍啦! 「噢--!不是有放上天的烟花嘛!」Karen 兴奋地喊道. 「啊等等--」Yoko 惊呼.Karen 手持点燃引信的烟花,「嗯??」 Yoko 最希望见到的是排列优美的烟火,当然不会放过这个机会-