1. 程式人生 > 實用技巧 >洛谷P4514 上帝造題的七分鐘

洛谷P4514 上帝造題的七分鐘

這是一個二維區域修改區域查詢問題,可以考慮使用二維樹狀陣列解決。

我們先回憶一下一維區間修改區間查詢樹狀陣列是怎麼做的,因為樹狀陣列本身只支援求字首和的形式,每次修改也只能單點修改,因此結合字首和和單點修改的性質,我們想到可以用樹狀陣列來維護差分陣列,這樣就能做到區間修改單點查詢,但這還是不是我們需要的區間修改區間查詢。上面的差分陣列給了我們區間修改的啟發,下面我們令 \(d\) 為差分陣列,我們發現區間查詢同樣可以差分,於是我們的目標實際上是求這樣一個式子(\(1 \sim i\) 的字首和):

\[\sum\limits_{i = 1} ^ n \sum\limits_{j = 1} ^ i d_j \]

可以考慮計算每個 \(d_j\) 對答案的貢獻,即:

\[\sum\limits_{i = 1} ^ n d_i \times (n - i + 1) \]

為了能接下來我們讓 \(d_i\)\(i\) 放在一起,將每次詢問不同的 \(n\) 拿開方便維護:

\[(n + 1)\sum\limits_{i = 1} ^ n d_i - \sum\limits_{i = 1} ^ n d_i \times i \]

於是我們驚奇地發現我們只需要維護出 \(\sum\limits_{i = 1} ^ n d_i\) 以及 \(\sum\limits_{i = 1} ^ n d_i \times i\)

即可。於是我們可以寫出如下程式碼:

void add(int p, int k){
    for(int i = p; i <= n; i += lowbit(i)) C1[i] += k, C2[i] += p * k;
}
int query(int p){
    int ans = 0;
    for(int i = p; i; i -= lowbit(i)) ans += (p + 1) * C1[i] - C2[i];
    return ans;
}
signed main(){
    n = read(), m = read();
    rep(i, 1, n) a[i] = read(), d[i] = a[i] - a[i - 1], add(i, d[i]);
    rep(i, 1, m){
        opt = read(), l = read(), r = read();
        if(opt == 1) k = read(), add(l, k), add(r + 1, -k);
        else printf("%lld\n", query(r) - query(l - 1));
    }
    return 0;
}

同樣類似於上面那個思路,我們可以定義一下二維陣列的差分,即我們需要讓 \((1, 1) \sim (n, m)\) 的字首和為 \(a_{i, j}\),可以考慮二維字首和的式子 \(S_{i, j} = S_{i - 1, j} + S_{i, j - 1} - S_{i - 1, j - 1} + a_{i, j} = a_{i - 1, j} + a_{i, j - 1} - a_{i - 1, j - 1} + a_{i, j}\),於是我們只需令差分陣列 \(d_{i, j} = a_{i, j} - a_{i - 1, j} - a_{i, j - 1} + a_{i - 1, j - 1}\) 即可。不難發現我們每次對某個位置 \((i, j)\) 進行修改,影響的是 \((i, j) \sim (n, m)\) 這一段區間,於是我們我們要給二維的一片區域 \((a, b) \sim (c, d)\) 加上某個數 \(x\),只需要在 \((a, b)\) 出加上 \(x\),在 \((a, d + 1), (c + 1, b)\) 出減去 \(x\),再在 \((c + 1, d + 1)\) 處加上 \(x\) 即可。因為可以使用差分,因此我們每次需要查詢的就是 \((1, 1) \sim (n, m)\) 這個區域的和,即:

\[\sum\limits_{i = 1} ^ n \sum\limits_{j = 1} ^ m \sum\limits_{k = 1} ^ i \sum\limits_{l = 1} ^ j d_{k, l} \]

同樣的我們考慮每個 \(d_{k, l}\) 對答案的貢獻,顯然 \(d_{i, j}\) 會被 \((i, j) \sim (n, m)\) 這個區域內的所有數算一次,於是原式即:

\[\sum\limits_{i = 1} ^ n \sum\limits_{j = 1} ^ m d_{i, j} \times (n - i + 1) \times (m - j + 1) \]

\[= \sum\limits_{i = 1} ^ n \sum\limits_{j = 1} ^ m d_{i, j} \times (nm + n + m + 1 - mi - i - nj - j + ij) \]

同樣讓每個位置維護的值只與每個位置有關,將查詢的部分提出來有:

\[(nm + n + m + 1) \sum\limits_{i = 1} ^ n \sum\limits_{j = 1} ^ m d_{i, j} - (m + 1)\sum\limits_{i = 1} ^ n \sum\limits_{j = 1} ^ m d_{i, j} \times i - (n + 1)\sum\limits_{j = 1} ^ m d_{i, j} \times j + \sum\limits_{j = 1} ^ m d_{i, j} \times ij \]

於是我們每個位置維護四個值:\(\sum\limits_{i = 1} ^ n \sum\limits_{j = 1} ^ m d_{i, j}, \sum\limits_{j = 1} ^ m d_{i, j} \times i, \sum\limits_{j = 1} ^ m d_{i, j} \times j, \sum\limits_{j = 1} ^ m d_{i, j} \times ij\) 即可。

#include<bits/stdc++.h>
using namespace std;
#define N 2050 + 5
#define lowbit(x) (x & (-x))
char opt[N];
int n, m, a, b, c, d, k, C[N][N][4];
int read(){
    char c; int x = 0, f = 1;
    c = getchar();
    while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
void add(int x, int y, int k){
    if(x < 1 || x > n || y < 1 || y > m) return;
    int a = k * x, b = k * y, c = k * x * y;
    for(int i = x; i <= n; i += lowbit(i))
        for(int j = y; j <= m; j += lowbit(j))
            C[i][j][0] += k, C[i][j][1] += a, C[i][j][2] += b, C[i][j][3] += c;
}
int query(int x, int y){
    if(x < 1 || x > n || y < 1 || y > m) return 0;
    int a = 0, b = 0, c = 0, d = 0;
    for(int i = x; i; i -= lowbit(i))
        for(int j = y; j; j -= lowbit(j))
            a += C[i][j][0], b += C[i][j][1], c += C[i][j][2], d += C[i][j][3];
    return a * (x * y + x + y + 1) - b * (y + 1) - c * (x + 1) + d;
}
int main(){
    n = read(), m = read();
    while(scanf("%s", opt + 1) != EOF){
        a = read(), b = read(), c = read(), d = read();
        if(opt[1] == 'L'){
            k = read();
            add(a, b, k), add(c + 1, d + 1, k);
            add(a, d + 1, -k), add(c + 1, b, -k);
        }
        else printf("%d\n", query(c, d) - query(c, b - 1) - query(a - 1, d) + query(a - 1, b - 1));
    }
    return 0;
}