分塊(簡單版樹狀數組,線段樹)
前言
首先,在NOIP的比賽裏分塊是一個很好的水分神器,因為它可以代替樹狀數組,線段樹,但是如果出題人要卡你的程序的話......
分塊思想
包含n個元素的整數數組A,每次可以 C(i, j) : 修改一個元素A[i] = j Q(i, j) : 詢問A[i]+A[i+1]+…+A[j]的值
如何設計算法,使得修改和詢問操作的時間復雜度盡量低? 顯然存在修改O(1),查詢O(n)的算法。
每次重新計算,一些未被修改區間也經常被重復求和,於是我們可以將A[1..n]均分成sqrt(n)塊,每塊內部的元素為sqrt(n)個。
修改操作時只需要修改A[i]及A[i]所在的塊。
詢問操作時是需要枚舉i和j所在的塊內部,及其之間的塊。
時間復雜度在O(sqrt(n))級別。
主要思想就是將待操作的長度為N的區間分成大小為sqrt(N)的塊,然後實現各種操作……
一些常用定義:
MAGIC:定義一個塊的大小,如字面意思,一個莫名其妙的數字……
於是,我們把一段長度為N的區間,分成了若幹長度為 MAGIC 的區間:[0,magic),[magic, 2magic)....
於是易得,i / MAGIC 就是點 i 所在塊的編號,若 i % MAGIC == 0,則證明由點 i 開始是一個新區間
一般來講,我們在預處理和修改的時候,維護兩個信息,一個是序列,另一個是塊
例題
講了這麽多還是來看例題吧......
鏈接點我!!!
#6277. 數列分塊入門 1
題目描述
給出一個長為 n的數列,以及 n 個操作,操作涉及區間加法,單點查值。
輸入格式
第一行輸入一個數字 n。
第二行輸入 n 個數字,第 iii 個數字為 ai??,以空格隔開。
接下來輸入 n 行詢問,每行輸入四個數字 opt、l、r、c,以空格隔開。
若 opt=0,表示將位於 [l,r] 的之間的數字都加 c。
若 opt=1,表示詢問 ar 的值(l 和 c 忽略)。
輸出格式
對於每次詢問,輸出一行一個數字表示答案。
樣例
樣例輸入
4
1 2 2 3
0 1 3 1
1 0 1 0
0 1 2 2
1 0 2 0
樣例輸出
2
5
數據範圍與提示
對於 100% 的數據,1≤n≤50000,−231≤others、ans≤231−1。
題解:
(建議大家邊看程序邊題解)
這是一題區間修改,單點查詢可以用樹狀數組,線段樹(這不是放*嗎)
但是我們在這裏考慮分塊;
首先,我們開一個最普通的數組來記錄每個元素的權值;
然後再開一個數組來記錄每個元素所對應的分塊;
然後再再(語文有點不好......)開一個數組來記錄每個分塊要加的值;
每次尋找 l , r ,之間的區間,在分塊內部的直接打上標記;
如果在分塊外面的直接暴力修改;
嗯,代碼......
1 //NOIPRP++ 2 #include<bits/stdc++.h> 3 #define Re register int 4 using namespace std; 5 int N,a[50005],f[50005],opt,l,r,c,Sqr,Tage[50005]; 6 inline void read(int &x){ 7 x=0; char c=getchar(); bool p=1; 8 for (;‘0‘>c||c>‘9‘;c=getchar()) if (c==‘-‘) p=0; 9 for (;‘0‘<=c&&c<=‘9‘;c=getchar()) x=(x<<1)+(x<<3)+(c^48); 10 p?:x=-x; 11 } 12 inline void Add(int l,int r,int c){ 13 for (Re i=l;i<=min(f[l]*Sqr,r);i++) a[i]+=c; 14 if (f[l]^f[r]) for (Re i=(f[r]-1)*Sqr+1;i<=r;i++) a[i]+=c; 15 for (Re i=f[l]+1;i<=f[r]-1;i++) Tage[i]+=c; 16 } 17 int main(){ 18 Re i,j; 19 read(N);Sqr=sqrt(N); 20 for (i=1;i<=N;i++) f[i]=(i-1)/Sqr+1; 21 for (i=1;i<=N;i++) read(a[i]); 22 for (i=1;i<=N;i++){ 23 read(opt);read(l);read(r);read(c); 24 if (opt==0) Add(l,r,c); 25 if (opt==1) printf("%d\n",a[r]+Tage[f[r]]); 26 } 27 return 0; 28 } 29 //NOIPRP++View Code
分塊(簡單版樹狀數組,線段樹)