1. 程式人生 > >BZOJ4785 [Zjoi2017]樹狀數組 【二維線段樹 + 標記永久化】

BZOJ4785 [Zjoi2017]樹狀數組 【二維線段樹 + 標記永久化】

結合 題解 php 數組 air line pri www ostream

題目鏈接

BZOJ4785

題解

肝了一個下午QAQ沒寫過二維線段樹還是很難受

首先題目中的樹狀數組實際維護的是後綴和,這一點憑分析或經驗或手模觀察可以得出
\(\mod 2\)意義下,我們實際求出的區間和是\([l - 1,r - 1]\),和\([l,r]\)唯一不同的就在於\(l - 1\)\(r\)
所以每個詢問實際是詢問兩個位置值相同的概率

我們把詢問看做二元組\((a,b)\),其中\(a \le b\),我們要維護\((a,b)\)不同的概率【至於為什麽是不同而不是相同,等下說】
初始概率都為\(0\)
對於修改操作\([l,r]\)
\(a \in [1,l - 1],b \in [l,r]\)

時,此時有\(\frac{1}{len}\)的概率改變不等關系
\(a \in [l,r],b \in [r + 1,n]\)時,此時有\(\frac{1}{len}\)的概率改變不等關系
\(a,b \in [l,r]\)時,此時有\(\frac{2}{len}\)的概率改變不等關系

所以我們可以使用二維線段樹維護這些區域的概率值
如果原不等概率為\(p_0\),現在有\(p_1\)的概率改變不等關系
那麽新的概率\(p‘ = p_0(1 - p_1) + (1 - p_0)p_1 = p_0 + p_1 - 2p_0p_1\)
我們記其為概率的合並
經計算可以得出,這樣的合並滿足交換律結合律單位元

\(0\)
所以我們可以使用線段樹很方便地維護
同時為了簡化操作,我們采用標記永久化
現在就可以理解我們為什麽要維護不等概率了,因為初值為\(0\),恰好也為合並運算的單位元

二維線段樹標記永久化的姿勢:
內層線段樹將標記儲存在路徑上的節點中
外層線段樹則通過在修改中途訪問內層線段樹而就此停止,在詢問時詢問路徑上所有的內層線段樹即可

還要註意空間大小,\(O(nlog^2n)\)要開足夠大的空間

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map> #define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt) #define REP(i,n) for (int i = 1; i <= (n); i++) #define mp(a,b) make_pair<int,int>(a,b) #define cls(s) memset(s,0,sizeof(s)) #define cp pair<int,int> #define LL long long int using namespace std; const int maxn = 500005,maxm = 40000005,INF = 1000000000,P = 998244353; inline int read(){ int out = 0,flag = 1; char c = getchar(); while (c < 48 || c > 57){if (c == ‘-‘) flag = -1; c = getchar();} while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();} return out * flag; } int qpow(int a,int b){ int re = 1; for (; b; b >>= 1,a = 1ll * a * a % P) if (b & 1) re = 1ll * re * a % P; return re; } int inv(int x){return qpow(x,P - 2);} int merge(int x,int y){ return (((x + y) % P - 2ll * x * y % P) + P) % P; } int n,m,ls[maxm],rs[maxm],val[maxm],rt[maxn << 2],cnt,ans; void modify(int& u,int l,int r,int L,int R,int v){ if (!u) u = ++cnt; if (l >= L && r <= R){ val[u] = merge(val[u],v); return; } int mid = l + r >> 1; if (mid >= L) modify(ls[u],l,mid,L,R,v); if (mid < R) modify(rs[u],mid + 1,r,L,R,v); } void query(int u,int l,int r,int pos){ if (!u) return; ans = merge(ans,val[u]); if (l == r) return; int mid = l + r >> 1; if (mid >= pos) query(ls[u],l,mid,pos); else query(rs[u],mid + 1,r,pos); } void Modify(int u,int l,int r,int L,int R,int ll,int rr,int v){ if (l >= L && r <= R){ modify(rt[u],1,n,ll,rr,v); return; } int mid = l + r >> 1; if (mid >= L) Modify(u << 1,l,mid,L,R,ll,rr,v); if (mid < R) Modify(u << 1 | 1,mid + 1,r,L,R,ll,rr,v); } void Query(int u,int l,int r,int Pos,int pos){ if (rt[u]) query(rt[u],1,n,pos); if (l == r) return; int mid = l + r >> 1; if (mid >= Pos) Query(u << 1,l,mid,Pos,pos); else Query(u << 1 | 1,mid + 1,r,Pos,pos); } int main(){ //freopen("in.in","r",stdin); //freopen("out1.txt","w",stdout); n = read(); m = read(); int opt,l,r,len,p; while (m--){ opt = read(); l = read(); r = read(); if (opt & 1){ len = r - l + 1; p = inv(len); if (l > 1){ Modify(1,1,n,1,l - 1,l,r,p); modify(rt[0],1,n,1,l - 1,1); } if (r < n){ Modify(1,1,n,l,r,r + 1,n,p); modify(rt[0],1,n,r + 1,n,1); } Modify(1,1,n,l,r,l,r,2 * p % P); modify(rt[0],1,n,l,r,(1 - p + P) % P); } else { ans = 0; if (l > 1) Query(1,1,n,l - 1,r); else query(rt[0],1,n,r); printf("%d\n",(1 - ans + P) % P); } } return 0; }

BZOJ4785 [Zjoi2017]樹狀數組 【二維線段樹 + 標記永久化】