1. 程式人生 > >【bzoj4785】[Zjoi2017]樹狀數組 線段樹套線段樹

【bzoj4785】[Zjoi2017]樹狀數組 線段樹套線段樹

奇怪 原因 tdi function 數字 二進制位 操作 接下來 還需

題目描述

漆黑的晚上,九條可憐躺在床上輾轉反側。難以入眠的她想起了若幹年前她的一次悲慘的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;
}

【bzoj4785】[Zjoi2017]樹狀數組 線段樹套線段樹