BZOJ2683: 簡單題(CDQ分治 + 樹狀數組)
BZOJ2683: 簡單題(CDQ分治 + 樹狀數組)
- 題意:
你有一個\(N*N\)的棋盤,每個格子內有一個整數,初始時的時候全部為\(0\),現在需要維護兩種操作:
命令 | 參數限制 | 內容 |
---|---|---|
\(1\ x\ y\ A\) | \(1\le x,y \le N\),A是正整數 | 將格子\(x,y\)裏的數字加上\(A\) |
\(2\ x1\ y1\ x2\ y2\) | \(1\le x1\le x2\le N,1\le y1\le y2\le N\) | 輸出\(x1\ y1\ x2\ y2\)這個矩形內的數字和 |
\(3\) | 無 | 終止程序 |
? \(1<=N<=500000\),操作數不超過\(200000\)
- 題解:
這個題是cdq分治的裸題吧。
一維:時間(按輸入順序就行了)
二維:\(x\)坐標(cdq分治)
三維:\(y\)坐標(樹狀數組)
這個題比較裸,但是cdq分治細節還是有一點的(調的錯誤我可以列一版了。。)
?
- 算法講解:
但我想簡單講一下cdq分治(因為網上很多都很坑沒講清楚)
cdq是專門解決多維偏序的問題,比如像這一道題統計二維矩形的權值,或者直接求高維偏序的個數。
如果不用cdq分治,就只能樹套樹或者KD-tree這種巨型工業數據結構。而且樹套樹常常空間和常數都很恐怖,並且很難寫……
cdq分治是個比較好寫的東西,但其中的思想十分的巧妙和神奇。
你應該學過歸並排序求逆序對吧,那是最裸的cdq了。他就是利用了左邊的答案來更新右邊的答案,cdq就是在這個方面不同於普通的分治。
它每次算答案,只能在右邊區間算也就是\([mid+1,r]\)。這是為什麽可以這樣呢,因為你初始給它的序列,按這樣算的話,絕對只會算它原序列左邊的貢獻,不會算到右邊去。(想一想,為什麽) 這個只需要自己模擬下分治的區間劃分和左右區間考慮就行了。
這就可以會強制使你一開始的那一維有序,對答案計算是正確的。(但切記最後給你的序列不一定是按你給它的順序了!!!)
然後它中間會有一個排序比較的過程,這就可以使第二個維度變得有序了。(最後的序列一定是第二維度有序的) 然後根據前兩個維度算答案就行了,後面的維度全都是附加在這兩個維度上面的。
總的步驟:
- 分開(遞歸計算左區間和右區間)
- 計算(用左區間來統計,右區間來加上貢獻)
- 合並(將當前序列變得有序)
- 又回到題解:
這道題,就是對於所有操作進行cdq分治(一般都是對於操作進行分治)。
第三維用樹狀數組統計\(y\)的前綴和就行了,因為\(x\)已經排好序了,所以可以直接算了。
左區間只執行Add
操作,右區間只執行Sum
操作。
對於一個詢問操作,要將它拆成4個詢問操作(就是類似詢問二維前綴和),加加減減就行了。
註意幾個細節(我調了很久的點)
- 樹狀數組清空的時候,下標不是
val
而是y
; - 拆矩陣的時候,一定要不要寫錯下標;
然後多拍幾組,寫個暴力很容易查出來的。
- 代碼:
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), _end_ = (int)(r); i <= _end_; ++i)
#define Fordown(i, r, l) for(register int i = (r), _end_ = (int)(l); i >= _end_; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;
inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar() ) if (ch == ‘-‘) fh = -1;
for (; isdigit(ch); ch = getchar() ) x = (x<<1) + (x<<3) + (ch ^ ‘0‘);
return x * fh;
}
void File() {
#ifdef zjp_shadow
freopen ("P2683.in", "r", stdin);
freopen ("P2683.out", "w", stdout);
#endif
}
int n;
const int N = 800100;
struct Opt {
int x, y, type, id, val;
inline bool operator < (const Opt &rhs) const {
return (x ^ rhs.x) ? x < rhs.x : type < rhs.type;
}
};
Opt lt[N], tmp[N];
#define lowbit(x) (x & -(x))
struct Fenwick_Tree {
int c[500100];
inline void Add(int pos, int val) { for (; pos <= n; pos += lowbit(pos) ) c[pos] += val; }
inline int Sum(int pos) { int res = 0; for (; pos; pos -= lowbit(pos) ) res += c[pos]; return res; }
inline void Clear(int pos) { for (; pos <= n; pos += lowbit(pos) ) if (c[pos]) c[pos] = 0; else break; }
};
Fenwick_Tree T;
int ans[N];
void Cdq(int l, int r) {
if (l == r) return ;
int mid = (l + r) >> 1;
Cdq(l, mid); Cdq(mid + 1, r);
int lp = l, rp = mid + 1, o = l;
while (lp <= mid && rp <= r) {
if (lt[lp] < lt[rp]) {
if (lt[lp].type == 1) T.Add(lt[lp].y, lt[lp].val);
tmp[o ++] = lt[lp ++];
} else {
if (lt[rp].type == 2) ans[lt[rp].id] += lt[rp].val * T.Sum(lt[rp].y);
tmp[o ++] = lt[rp ++];
}
}
while (lp <= mid) tmp[o ++] = lt[lp ++];
while (rp <= r) {
if (lt[rp].type == 2) ans[lt[rp].id] += lt[rp].val * T.Sum(lt[rp].y);
tmp[o ++] = lt[rp ++];
}
For (i, l, mid) if (lt[i].type == 1) T.Clear(lt[i].y);
For (i, l, r) lt[i] = tmp[i];
}
int qcnt, acnt;
inline void Addq(int x, int y, int type, int id, int val) {
lt[++qcnt] = (Opt){x, y, type, id, val};
}
int main () {
File() ;
n = read();
for (;;) {
int opt = read();
if (opt == 3) break ;
int xa, ya, xb, yb, val;
if (opt == 1) {
xa = read(); ya = read(); val = read();
Addq(xa, ya, 1, 0, val);
} else {
xa = read(); ya = read();
xb = read(); yb = read();
Addq(xa - 1, ya - 1, 2, (++ acnt), 1);
Addq(xa - 1, yb, 2, acnt, -1);
Addq(xb, ya - 1, 2, acnt, -1);
Addq(xb, yb, 2, acnt, 1);
}
}
Cdq(1, qcnt);
For (i, 1, acnt) printf ("%d\n", ans[i]);
return 0;
}
?
BZOJ2683: 簡單題(CDQ分治 + 樹狀數組)