HDU 4872 ZCC Loves COT 對標記的標記打標記

原題:http://acm.hdu.edu.cn/showproblem.php?pid=4872

題意:給一個n*n*n的方體,定義兩種操作,修改操作是給出x,y,z,a,把滿足0 <= k <= j <= i < a的所有val[x+i][y+j][z+k]的值+1,詢問操作類似詢問這個區域的val和,更新和查詢都是10W,n是100,所有更新都結束後纔會開始查詢


解題思路:

首先請看此題的一維版本:http://acm.hdu.edu.cn/showproblem.php?pid=1556

這道題目就很經典了,對於一次更新[l, r], 我們操作 a[l]++, a[r+1]-- ,然後求一次前綴和,就可以得到具體的 a[i] 的值

原理就是遇到+1表示有一次塗色從這裏開始,遇到-1表示有一次塗色到此結束,具體就不在詳細說了

對於這樣的操作我們稱爲打標記


然後我們把他擴展到二維版本

定義修改操作是給出 x, y, a 對滿足 0 <= j <= i < a 的所有 val[x+i][y+j] 的值 +1, 詢問類似

我們在平面首先畫出這樣一個區域


向下的是x + i的部分,向右是y + j部分,滿足 j <= i 的部分畫出來就是這樣一個三角形

那麼我們仿照一維的情況,對於每一行打標記,就變成了下面這樣


這時我們可以發現,標記是連續的一段,相當於我們對一維的線段打的兩個點標記,在線段擴展到二維平面的同時,被拉伸成了線段

那麼對於這個由標記生成的線段,我們仍然可以打標記,對於兩個方向,分別打標記,一共四個,效果如下


豎直方向的+1, -1是每行 + 標記的標記,斜線方向的則是 - 的標記

注意 - 標記也是繼續按 +1 -1來打標記,這時的前綴和表示有多少個 - 標記,而不是減了多少

然後對於這兩種標記,我們分別先沿向下和向右下的方向做一次前綴和,就可以還原出一維標記的情況

然後再從左向右累加,就可以得到具體元素的值


那麼當我們得到具體元素的值之後,如何在O(1)效率求解任意三角形區域的sum值,還需要進一步討論

根據一維前綴和,我們可以知道要想O(1)求解,必需通過類似前綴相減的方式,那麼前綴本身不一定是線性的,只要他沿着若干方向一直擴展到邊界,就可以是認爲是前綴和

我們先討論一個比較特殊的情況,就是詢問的 x == y,如下圖


Q點是我們詢問的座標(x, y), △QAD的區域就是我們要求sum值,通過一陣腦洞可以發現

△QAD = △AOB - △ QOC - 四邊形CBDQ

而 四邊形CBDQ = 四邊形OBDE - 四邊形OCQE

轉化到這一步,所有的變量我們都可以通過一定姿勢的前綴和求得了,我們用 tri[][] 來表示從點到x軸和原點構成的三角形區域,如 tri[A.x][A.y] = △AOB

再用 rec[][] 表示點到x軸y軸構成的區域,如 rec[D.x][D.y] = 四邊形OBDE

則有 △QAD = tri[A] - tri[Q] - (rec[D] - rec[Q]) , tri和rec的預處理就很容易了,藉助一下每行的前綴 pre[][]即可


另外一種情況就是 x != y, 這個時候和上面不一樣的地方就是AQ延長線不過原點,那麼圖形不規則,似乎不符合我們上面的規律

但實際上由於外面區域都爲0,所以可以假象擴大座標系,把他弄到原點去,所以我們預處理前綴時只要按方向累加即可,不需要考慮太多


下面是將本題退化到二維情況的代碼:

部分數組的含義如上文所示

其他的如 add[][] 表示一維 + 標記的再標記,sub爲一維 - 標記的再標記

由於for循環的嵌套過於喪心病狂(主要是三維的時候),於是我第一次define了for循環,REP什麼的都比較常見了

<span style="font-family:Courier New;">//
#include <iostream>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdlib>
using namespace std;

#define LL long long
#define REP(i,a,b) for(int i = a; i <= b; ++i)

typedef LL Node[105][105];

Node val, add, sub, pre, tri, rec;

void init() {
    memset(add, 0, sizeof(add));
    memset(sub, 0, sizeof(sub));
}

void upd(int x, int y, int a) {
    ++add[x][y];
    --add[x + a][y];
    ++sub[x][y + 1];
    --sub[x + a][y + a + 1];
}

void solve(int n) {
    REP(i,1,n) REP(j,1,n) {
        add[i][j] += add[i - 1][j];
        sub[i][j] += sub[i - 1][j - 1];
        val[i][j] = val[i][j - 1] + add[i][j] - sub[i][j];
        pre[i][j] = pre[i][j - 1] + val[i][j];
        rec[i][j] = rec[i - 1][j] + pre[i][j];
        tri[i][j] = tri[i - 1][j - 1] + pre[i][j];
    }
}

LL cal(int x, int y, int a) {
    --x, --y;
    LL ans = 0;
    ans += tri[x + a][y + a] - tri[x][y];
    ans -= rec[x + a][y] - rec[x][y];
    return ans;
}

int main()
{
    int n, m, q, x, y, a;
    while( scanf( "%d%d%d", &n, &m, &q ) != EOF ) {
        init();
        while( m-- ) {
            scanf( "%d%d%d", &x, &y, &a );
            upd(x, y, a);
        }
        solve(n);
        while( q-- ) {
            scanf( "%d%d%d", &x, &y, &a );
            printf( "%lld\n", cal(x, y, a) );
        }
    }
    return 0;
}</span>


那麼下面我們繼續討論擴展到三維的情況:

友情提示請先弄清楚二維的情況,並自備墨鏡,下面的圖形將開始各種喪心病狂


首先我們根據一維到二維的情況,一維的標記擴展後被拉伸成了線段,那麼二維的標記在擴展到三維的情況時,是不是也一樣是拉伸成線段?

這個不難想通,把上面那個二維標記的平面,沿z軸向上拉伸,三角形變成四面體,四個標記變成了四個線段

實質上把拉伸後的每一層(垂直 z 軸切下來一片),他和上面二維的情況是完全一致的,只是三角形的大小在變化,圖形如下


這個圖形是在更新點Q展開的座標系,不是從原點,四面體的性質可以這樣考慮

原來是一個方體,爲了保證i >= j,垂直XOY平面沿x == y切一刀,同理垂直YOZ平面對y == z切一刀,就行了

首先我們考慮QAB那個三角形,其實就是二維那個三角形,然後把這個三角形像C拉伸,那些標記也沿着拉伸方向變成線段

所以我們可以在那四個標記的位置再打標記 +1,在C點附近四個線段結束的地方再打標記 -1

具體的二維標記拉伸出來的效果我就不畫了,沿着箭頭方向一路寫過去就行,你們腦補一下....


然後我們從這四個方向前綴和一下(當然我們又得開4個數組來記錄這四條線段的信息), 就可以還原二維的標記add 和 sub數組,然後z那一位就搞定了,問題退化成二維狀態

我之後的代碼分別用A, B, C, D 四個數組來處理,如果你想通了二維的情況,這裏就不難理解了,否則基本GG思密達


接下來我們再討論怎麼O(1)求這個四面體的Sum(val)

根據我們二維是得到的結論,我們用特殊圖形(x == y)討論出的規律,對任意情況適用,所以我們這裏仍然使用(x == y == z)的特殊點來討論

下面這個圖比較殘暴


我們要求的就是 四面體QADE

首先我們看到那個顯眼的點A,可以想(nao)到(bu)應該用 四面體OABC - 四面體OPQR

所以我們用 tet[A] 來保存類似四面體OABC的前綴

然後我們還需要減去 三棱柱DCF-QPR 和 三棱柱QDE-RFB

對於三棱柱DCF-QPR 我們顯然可以通過保存到YOZ平面的三棱柱的總和 psm[][][],用psm[D] - psm[Q]來獲得這一部分的值

那麼還剩下 三棱柱QDE-RFB,你看到RFB那個三角形,就差不多能聯想到二維那個圖形了,是一樣的

RFB = OBC - ORP - (PCFR) ,這裏PCFR怎麼來也很顯然,把這四個前綴都加一維就可以了

於是我們需要四類前綴來獲得QADE的值,當然預處理這四類前綴還需要其他的輔助數組


最終代碼:

部分上文提到的變量就不說了

pre[i][j][k] 保存 val[i][j][1 ~ k], sum[][][]保存類似QPR那樣的三角形片,用來再沿x方向累加得到psm, tet什麼的

tot[][][] 用了保存需要用到的那個長方體

(PS:這一部分還是弄懂了自己推好點,用到的變量名太多了,英語渣的表示壓力很大)

<span style="font-family:Courier New;">//
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

#define LL long long
#define REP(i,a,b) for(int i = a; i <= b; ++i)

typedef LL Node[105][105][105];

Node val, add, sub, A, B, C, D;
Node pre, sum, tot, tri, rec, psm, tet;

void init() {
    memset(A, 0, sizeof(A));
    memset(B, 0, sizeof(B));
    memset(C, 0, sizeof(C));
    memset(D, 0, sizeof(D));
}

void upd(int x, int y, int z, int a) {
    ++A[x][y][z];
    --A[x + a][y + a][z + a];
    ++B[x + a][y][z];
    --B[x + a][y + a][z + a];
    ++C[x][y + 1][z];
    --C[x + a][y + a + 1][z + a];
    ++D[x + a][y + a + 1][z];
    --D[x + a][y + a + 1][z + a];
}

void solve(int n) {
    REP(i,1,n) REP(j,1,n) REP(k,1,n) {
        A[i][j][k] += A[i - 1][j - 1][k - 1];
        B[i][j][k] += B[i][j - 1][k - 1];
        C[i][j][k] += C[i - 1][j - 1][k - 1];
        D[i][j][k] += D[i][j][k - 1];
    }
    REP(i,1,n) REP(j,1,n) REP(k,1,n) {
        add[i][j][k] = A[i][j][k] - B[i][j][k];
        add[i][j][k] += add[i - 1][j][k];
        sub[i][j][k] = C[i][j][k] - D[i][j][k];
        sub[i][j][k] += sub[i - 1][j - 1][k];
        val[i][j][k] = val[i][j - 1][k] + add[i][j][k] - sub[i][j][k];
    }
    REP(i,1,n) REP(j,1,n) REP(k,1,n) {
        pre[i][j][k] = pre[i][j][k - 1] + val[i][j][k];
        sum[i][j][k] = sum[i][j - 1][k - 1] + pre[i][j][k];
        tot[i][j][k] = tot[i][j - 1][k] + pre[i][j][k];
        tri[i][j][k] = tri[i - 1][j - 1][k] + tot[i][j][k];
        rec[i][j][k] = rec[i - 1][j][k] + tot[i][j][k];
        psm[i][j][k] = psm[i - 1][j][k] + sum[i][j][k];
        tet[i][j][k] = tet[i - 1][j - 1][k - 1] + sum[i][j][k];
    }
}

LL cal(int x, int y, int z, int a) {
    --x, --y, --z;
    LL ans = 0, tmp = 0;
    ans += tet[x + a][y + a][z + a] - tet[x][y][z];
    ans -= psm[x + a][y][z] - psm[x][y][z];
    tmp += tri[x + a][y + a][z] - tri[x][y][z];
    tmp -= rec[x + a][y][z] - rec[x][y][z];
    ans -= tmp;
    return ans;
}

int main()
{
    int n, m, q, x, y, z, a;
    while( scanf( "%d%d%d", &n, &m, &q ) != EOF ) {
        init();
        while( m-- ) {
            scanf( "%d%d%d%d", &x, &y, &z, &a );
            upd(x, y, z, a);
        }
        solve(n);
        while( q-- ) {
            scanf( "%d%d%d%d", &x, &y, &z, &a );
            printf( "%lld\n", cal(x, y, z, a));
        }
    }
    return 0;
}</span>



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章