1. 程式人生 > >【BZOJ4785】[Zjoi2017]樹狀數組 樹套樹(二維線段樹)

【BZOJ4785】[Zjoi2017]樹狀數組 樹套樹(二維線段樹)

這也 現在 ont 平面 nbsp -s mil 比賽 turn

【BZOJ4785】[Zjoi2017]樹狀數組

Description

漆黑的晚上,九條可憐躺在床上輾轉反側。難以入眠的她想起了若幹年前她的一次悲慘的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) 得到的結果是正確的概率是多少。

Input

第一行輸入兩個整數 n, m。 接下來 m 行每行描述一個操作,格式如題目中所示。 N<=10^5,m<=10^5,1<=L<=R<=N

Output

對於每組詢問,輸出一個整數表示答案。如果答案化為最簡分數後形如 x/y,那麽你只需要輸出 x*y^?1 mod 998244353 後的值。(即輸出答案模 998244353)。

Sample Input

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

Sample Output

1
0
665496236
//在進行完 Add(3) 之後, A 數組變成了 [0, 1, 1, 0, 0]。所以前兩次詢問可憐的程序答案都是1,因此第一次詢問可憐一定正確,第二次詢問可憐一定錯誤。

題解:發現這裏給的樹狀數組的方向正好是反過來的,也就是說這裏的樹狀數組維護的實際上是後綴xor和。那麽後綴xor和與前綴xor和相等的情況就是:

[1,l-1]^[1,r]=[l-1,n]^[r,n] --> [l,r]=[l-1,r-1] ---> [l-1]=[r]

也就是說我們求的是l的值和r的值相等的概率。然後到這裏,大部分題解都說“這變成了一個二維數點問題”,然而本蒟蒻一臉mengbi,所以,這裏還是換一種方法講吧。

我們用(a,b)表示a的值和b的值相等的概率。加入我們想要修改[l,r]中隨機一個點,那麽我們先考慮所有l<=a<b<=r的點對。

對於點對a,b,一次修改中它們最多只有一個數改變,我們設$q=1-{2\over r-l+1}$,表示a,b相等性不變的概率,設p表示原來a,b相等的概率,那麽$p=pq+(1-p)(1-q)$。並且,我們要對[l,r]中所有的點對都進行這個計算,那麽我們可以認為(a,b)是二維平面上的一個點,我們要修改的是(l,l)-(r,r)這個矩形,這可以用二維線段樹維護。

//問題:對於某個樹上的節點x,我們先給它打了個標記q1,有想給它打個標記q2,這兩個標記該如何處理呢?自己推一推就知道,因為一開始的p都是1,那麽先處理q1和先處理q2的結果是相同的(也就是說標記滿足交換律),設p打了q1標記變成p‘,我們在同樣的給p‘打個q2標記就行了。

再考慮a<l<=b<=r的點對(l<=a<=r<b的類似),這樣的點對的相等性不變的概率就是$q=1-{1\over r-l+1}$。此時我們要修改的矩形就變成了(1,l-1)-(l,r),依舊二維線段樹。

突然發現一種情況,當l=1時怎麽辦?因為l-1=0,所以此時要求的就是後綴xor和與前綴xor和相等的概率,單獨維護一下就好了。

=======下面是二維線段樹部分=======

本題要支持什麽操作呢?矩形區間計算。因為二維線段樹必須標記永久化,所以我們在第一位線段樹時,每訪問到一個合法的整區間,就進入第二維線段樹去進行區間修改。這樣,在查詢的時候,我們的答案要將第一維線段樹上 從根到葉子的所有節點的查詢結果 全都算到一起,也就是說沒經過一個節點就要更新答案。

#include <cstdio>
#include <cstring>
#include <iostream>
#define z(_) (((_)%mod+mod)%mod)
#define lson x<<1
#define rson x<<1|1
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=100010;
int n,m,tot;
ll inv(ll x)
{
	ll z=1,y=mod-2;
	while(y)
	{
		if(y&1)	z=z*x%mod;
		x=x*x%mod,y>>=1;
	}
	return z;
}
ll calc(ll a,ll b)
{
	return z(a*b+(1-a)*(1-b));
}
int rd()
{
	int ret=0;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	gc=getchar();
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+gc-‘0‘,gc=getchar();
	return ret;
}
int ls[maxn<<8],rs[maxn<<8],rt[maxn<<2];
ll s[maxn<<8];
void up1(int l,int r,int &x,int a,int b,ll c)
{
	if(!x)	x=++tot,s[x]=1;
	if(a<=l&&r<=b)
	{
		s[x]=calc(s[x],c);
		return ;
	}
	int mid=l+r>>1;
	if(a<=mid)	up1(l,mid,ls[x],a,b,c);
	if(b>mid)	up1(mid+1,r,rs[x],a,b,c);
}
ll q1(int l,int r,int x,int a)
{
	if(!x)	return 1;
	if(l==r)	return s[x];
	int mid=l+r>>1;
	if(a<=mid)	return calc(s[x],q1(l,mid,ls[x],a));
	else	return calc(s[x],q1(mid+1,r,rs[x],a));
}
void up2(int l,int r,int x,int a,int b,int c,int d,ll e)
{
	if(a<=l&&r<=b)
	{
		up1(1,n,rt[x],c,d,e);
		return ;
	}
	int mid=l+r>>1;
	if(a<=mid)	up2(l,mid,lson,a,b,c,d,e);
	if(b>mid)	up2(mid+1,r,rson,a,b,c,d,e);
}
ll q2(int l,int r,int x,int a,int b)
{
	if(l==r)	return q1(1,n,rt[x],b);
	int mid=l+r>>1;
	if(a<=mid)	return calc(q1(1,n,rt[x],b),q2(l,mid,lson,a,b));
	else	return calc(q1(1,n,rt[x],b),q2(mid+1,r,rson,a,b));
}
int main()
{
	n=rd(),m=rd();
	int i,a,b,c;
	ll p,q;
	for(i=1;i<=m;i++)
	{
		c=rd(),a=rd(),b=rd();
		if(c==1)
		{
			p=inv(b-a+1);
			if(a>1)	up2(0,n,1,1,a-1,a,b,z(1-p)),up2(0,n,1,0,0,0,a-1,0);
			if(b<n)	up2(0,n,1,a,b,b+1,n,z(1-p)),up2(0,n,1,0,0,b+1,n,0);
			up2(0,n,1,a,b,a,b,z(1-2*p)),up2(0,n,1,0,0,a,b,p);
		}
		else	printf("%lld\n",q2(0,n,1,a-1,b));
	}
	return 0;
}

【BZOJ4785】[Zjoi2017]樹狀數組 樹套樹(二維線段樹)