推薦一個websocket測試工具:wscat
-
-
演算法訓練營第二章
-
-
-
名稱:分塊。
-
本質:優化後的暴力。
-
一些abstract:
設sn = 根號n。
樹狀陣列和線段樹維護的資訊必須滿足資訊合併特性,即對於區間來說,分成兩個區間分別操作和單獨對一個區間的操作是一樣的。不滿足這個特性,則不能使用樹狀陣列和線段樹。
分塊幾乎可以解決所有區間更新和區間查詢問題,但是效率差一些。分塊演算法就是把所有資料分成若干塊,維護塊內資訊,使得塊內查詢為O(1)時間,總查詢為O(sn)。
設資料是連續的n個,通常我們將資料分成sn塊,前面的塊大小都是sn,最後一塊可能不足這個值,也無所謂。pos[i]表示位置i所屬的塊號。對每個塊都進行資訊維護(暴力/懶標記)(懶標記用來存整個區間都進行的操作,只有不是整個區間的操作時才處理懶標記),分塊可以解決如下問題:
-
單點更新:所屬區間懶標記下傳,暴力更新,複雜度O(sn)。
-
區間更新:對於待修區間橫跨的整個塊,打上懶標記即可,顯然只有兩側的塊可能打不了,則下傳二者懶標記,再暴力修改覆蓋部分的值。因為整個塊的數量不超過sn,並且暴力修改的部分的長度小於2sn,下傳懶標記也是暴力改,所以下傳的運算元小於2sn,下傳完再直接修改不完整的部分,運算元也小於2sn,所以總共要進行的操作小於5sn,複雜度O(sn)。
-
區間查詢:和區間更新類似,也是整個塊的用塊儲存的資訊統計答案,兩端不完整的塊就暴力掃描,時間複雜度同上,為O(sn)。
-
-
-
操作:
-
預處理:將序列分塊,每個塊都標記左右端點的下標,存到L[i],R[i]中,對最後一個塊,要單獨賦值它的R[i]。分塊結果如下:
R[4] = n = 10
int t = (int)(sqrt(n * 1.0)), num = n / t;
if (n % num) num++; // 一共n個,分成了num塊,每一個塊大小為t
for (int i = 1; i <= num; i++) {
L[i] = (i-1)*t + 1; // 第i塊的起點
R[i] = i * t; // 第i塊的終點
}
R[num] = n; // 最後一塊越界了,需要改回來這裡以維護區間和為例:
// sum[i]表示第i塊的和,a存原始資料,pos[i]表示第i個數據屬於pos[i]塊
for (int i = 1; i <= num; i++) {
for (int j = L[i]; j <= R[i]; j++) {
pos[j] = i;
sum[i] += a[j];
}
} -
區間更新:比如將區間[l, r]區間的元素都加d(假設r不小於l)。
先求出l和r所屬的塊:
int p = pos[l], q = pos[r];
分類討論:如果同屬一塊,則直接暴力修改:(這裡因為只有加,懶標記只有一種,所以不用先下放懶標記,不然先後順序需要注意一下,比如有一個乘法的懶標記,再進行加操作,就得先下放乘法的懶標記,再執行這次的加法)
if (p == q) {
for (int i = l; i<= r; i++) {
a[i] += d;
}
sum[p] += 1ll * (r - l + 1);
}不屬於同一塊,則對中間完全覆蓋的塊打上懶標記,暴力修改不完整的兩個小塊:
else {
add[i] += d;
for (int i = p + 1; i <= q - 1; i++) {
add[i] += d; // 對中間完全覆蓋的塊打上懶標記
}
// 暴力修改不完整的兩個小塊
for (int i = l; i <= R[p]; i++) {
a[i] += d;
}
sum[p] += 1ll * d * (R[p] - l + 1);
for (int i = L[q]; i <= r; i++) {
a[i] += d;
}
sum[q] += 1ll * d * (r - L[q] + 1);
} -
區間查詢:查詢[l, r]區間的元素和。
先求出l和r所屬的塊:
int p = pos[l], q = pos[r];
long long ans = 0;分類討論:如果同屬一塊,則直接暴力累加,再加上懶標記上的值。
if (p == q) {
for (int i = l; i<= r; i++) {
ans += a[i];
}
ans += 1ll * add[p] * (r - l + 1);
}不屬於同一塊,則對中間完全覆蓋的塊打上懶標記,暴力修改不完整的兩個小塊:
else {
for (int i = p + 1; i <= q - 1; i++) {
ans += sum[i] + 1ll * add[i] * (R[i] - L[i] + 1); // 對中間完全覆蓋的塊打上懶標記
}
// 暴力修改不完整的兩個小塊
for (int i = l; i <= R[p]; i++) {
ans += a[i];
}
ans += 1ll * add[p] * (R[p] - l + 1);
for (int i = L[q]; i <= r; i++) {
ans += a[i];
}
sum[q] += 1ll * add[q] * (r - L[q] + 1);
}
printf("%lld", ans);
-
-
例題:
-
(HDU5057)給出一個數組,有兩個操作:
-
單點修改某個位置的值
-
區間查詢[l, r],指出這個區間中從低往高數第p位為q的個數是多少
給n個數,m個操作(都是十萬級別)。對於每個查詢,給出答案。
考慮分塊,先預處理最開始的每個區間的每個位的不同的值的數量,這個複雜度是O(10nlog(int))級別的。
對於查詢操作,只需要O(sn)級別。
對於修改操作,暴力修改即可,是O(1)級別。
-
-