題解 P3693 【琪露諾的冰雪小屋】

知識點: 模擬 , 信仰

原題面

大 型 車 萬 衆 自 裁 現 場ios

baka⑥


分析題意:

  1. 操做: ICE_BARRAGE R C D S
    • R:行 , C:列, D:方向 , S:強度
    • 在(R,C) 向 D 射線上 發射彈幕, 將 距離 S 格內 的位置 冰凍度 \(+1\)
      • 冰凍度 只是 地面的屬性 , 冰磚 無冰凍度屬性
      • 冰凍度 操做前已經爲 \(4\) 的方格 冰凍度 不改變
      • 遇到冰磚 射線線中止延伸
      • 距離 \(=\) 通過方格數量 , 通過格子數量 \(\ge S\) 時 直線中止延伸
    輸出: CIRNO FREEZED k BLOCK(S)
    • k : 冰凍度 \(+1\) 的位置的數量
  2. 操做: MAKE_ICE_BLOCK
    • 將 全地圖上 冰凍度 \(= 4\) 的 方格 冰凍度清零, 冰磚庫存數 \(+1\)
    輸出: CIRNO MADE x ICE BLOCK(S),NOW SHE HAS y ICE BLOCK(S)
    • x : 本次製造數 , y : 目前庫存數
  3. 操做: PUT_ICE_BLOCK R C H
    • R: 行 C:列 H:高
    • 庫存冰磚數\(-1\) , 在 三維空間中 \((R,C,H)\) 處放置 一個冰磚
    • 放置在地面 \(\Rightarrow\) 地面冰凍度清零
    輸出 :
    可能出現多種結果 , 按照輸出優先級排列 :
    1. CIRNO HAS NO ICE_BLOCK
      • 無庫存冰磚
      • 阻止操做進行
    2. BAKA CIRNO,CAN'T PUT HERE
      • 冰磚放置位置懸空 / 放置位置已有冰塊
        • 冰磚懸空 \(\Rightarrow\) 三維空間中 周圍六個位置 無冰磚存在
        • 對於 放置位置 接觸地面的 冰磚 , 不可能懸空
      • 阻止操做進行
    3. CIRNO MISSED THE PLACE
      • \(\text{R<HR}\ \text{或}\ \text{R>HR+HX-1}\)
        \(\text{或} \ \text{C<HC}\ \text{或}\ \text{C>HC+HY-1}\)
      • 放置在了 房子範圍之外的位置
      • 不可阻止 操做進行
    4. CIRNO PUT AN ICE_BLOCK INSIDE THE HOUSE
      • \(\text{HR+1}\le \text{R}\le \text{HR+HX-2}\)
        \(\text{且}\ \text{HC+1}\le \text{C}\le \text{HC+HY-2}\)
      • 冰磚放置在了 屋內 本應爲空地處
      • 不可阻止 操做進行
    5. CIRNO SUCCESSFULLY PUT AN ICE_BLOCK,NOW SHE HAS x ICE_BLOCK(S)
      • x : 目前 庫存冰磚數
      • 不考慮 是否堵住了 應該留門的位置
  4. 操做: REMOVE_ICE_BLOCK R C H
    • R: 行 C: 列 H: 高
    • \((R,C,H)\) 位置的冰磚取走 , 庫存 冰磚數 \(+1\)
    • 將 與地面接觸的 冰磚取走後 , 地面冰凍度歸零
    輸出:
    可能出現多種結果 , 按照輸出優先級排列:
    1. BAKA CIRNO,THERE IS NO ICE_BLOCK
      • 被操做位置 無冰磚
      • 阻止操做進行
    2. CIRNO REMOVED AN ICE_BLOCK,AND k BLOCK(S) ARE BROKEN
      • k: 碎掉冰塊數
      • 本次操做 致使 一冰磚聯通塊 懸空 (不與 與地面接觸的聯通塊 聯通)
        懸空 冰塊消失
      • 不可阻止 操做進行
    3. CIRNO REMOVED AN ICE_BLOCK
      • 成功取走 , 對其餘冰塊無影響
  5. 操做: MAKE_ROOF
    • 最後一 條操做 , 只出現一次
    • 按照下列順序進行 子操做, 按照 子操做個指令排列優先級 進行操做 ,
      若不知足條件, 子操做沒法進行 , 程序終止
    子操做:
    1. 搭建 房頂
      • 牆壁最高高度 \(+1\) 處 一次性放置 至多 \(\text{HX}\times \text{XY}\) 個冰磚,
      • 若 在 MAKE_ROOF 以前此高度處有冰磚 , 已有冰磚位置 沒必要重複放置
      • 比 此時放置的 屋頂的高度 高的 冰磚 視爲存在於房外
      不知足條件:
      可能出現多種結果 , 按照輸出優先級排列:
      1. SORRY CIRNO,NOT ENOUGH ICE_BLOCK(S) TO MAKE ROOF
        • 按照上述規則 , 庫存冰塊不足 填滿 房頂
      2. SORRY CIRNO,HOUSE IS TOO SMALL
        • 房頂建造完畢後 , 牆高 \(<2\) 或 房屋內部空間 \(<2\)(忽略屋內錯放冰磚)
    2. 移除 多餘冰磚:
      • 將 不屬於 房屋牆壁與 房屋頂部 的冰磚所有移走
      • 當 移走某冰磚時 , 可能 會致使 牆壁上 某冰磚 摔碎,
        則先將 牆壁上 會摔碎的冰磚 移走 (子任務3 中再進行填補)
        再 對原冰磚 進行移除
      輸出:
      k1 ICE_BLOCK(S) INSIDE THE HOUSE NEED TO BE REMOVED
      k2 ICE_BLOCK(S) OUTSIDE THE HOUSE NEED TO BE REMOVED
      • k1 : 房子內部 錯誤放置的 多餘冰磚數
        k2 : 房子外部 錯誤放置的 多餘冰磚數
      不知足條件 :
      1. SORRY CIRNO,HOUSE IS BROKEN WHEN REMOVING BLOCKS
        • 因爲 將牆壁上冰磚移走 , 致使房頂 不 與地面聯通 , 使房頂塌陷
    3. 填補 牆壁殘缺:
      • 牆壁 有殘缺的定義:
        • 除 門的候選位置 以外 , 從屋內向外看 還可看到其餘 殘缺
        • \(\color{red}{注意:}\) 當 門不得不 開到 柱子旁時 , 可看到 柱子對應位置 是否殘缺
          當 一個殘缺位置 同時與兩柱子相鄰時 , 可同時看到 兩柱子對應位置的殘缺
          此過程當中填補的全部殘缺 都屬於牆壁殘缺 (包括柱子殘缺)
      • 對於 高度\(\ge 2\) 的殘缺位置 , 必定須要被填補
      • 對於 高度 \(< 2\) 的殘缺位置 , 須要考慮 成爲門的狀況 :
        對於門的候選位置 , 應按照下列條件進行選擇 (按照必要性優先級排序) :git

        1. 房屋可以建成 (不出現 冰磚數量不足的狀況)
          • 對於門的候選位置 的選擇 , 須要使填補冰磚數量儘量 減小
            若門候選位置 相鄰的柱子殘缺 , 會致使 冰磚填補數量增長算法

          • 需填補冰磚數 \(=\) 牆壁殘缺數(不包括柱子) \(-\) 候選位置大小 \(+\) 可看到柱子殘缺數
            應將 門候選位置按照 "可看到柱子殘缺數\(-\)候選位置大小" 升序 做爲排序第一優先級
            • 顯然 , 可看到的柱子殘缺數 \(\le\) 門候選位置大小ide

            • 在此條件下, 可能出現: 選擇 大小爲 \(1\times 1\) 的殘缺 優於 大小爲 \(1\times 2\) 的殘缺 的狀況函數

        2. 存在一 \(1\times 2\) 大小的殘缺做爲 門
          • 知足條件\(1\) 的狀況下 ,
            優先選擇 高度爲 \(2\) 的殘缺 做爲 門的候選位置spa

            應將 門候選位置 按照 "殘缺 的高度" 降序 做爲排序 第二優先級調試

        3. 門 更靠近 牆壁的正中央
          • 知足 條件 \(1,2\) 的狀況下
            優先選擇 更靠近 牆壁正中央的 殘缺
            爲更接近 "CIRNO IS PERFECT!" 的狀況 (定義詳見 檢查 小屋\(6\)條)
          應將 門候選位置 按照 "與牆壁中間位置的距離" 升序 做爲排序 第三優先級
      不知足條件 :
      1. SORRY CIRNO,NOT ENOUGH ICE_BLOCKS TO FIX THE WALL
        • 通過上述過程, 減小填補冰磚數 後 ,
          庫存冰磚 仍是沒法 填補全部的 非門殘缺
          若是門開在 柱子旁 , 殘缺也 包括可看到的柱子 的殘缺
    4. 檢查 小屋
      1. 輸出: GOOD JOB CIRNO,SUCCESSFULLY BUILT THE HOUSE
        • 慶祝小屋完工 (不愧是琪露諾! 輕易就作到了咱們作不到的事)
      2. 檢查是否有 \(1\times 2\) 的門 :
        • 無 , 輸出 HOUSE HAS NO DOOR
        • 有 , 輸出 DOOR IS OK
      3. 檢查操做 \(3\) 中是否進行了填補操做
        • 否 , 輸出 WALL IS OK
        • 是 , 輸出 WALL NEED TO BE FIXED
      4. 檢查 四角的柱子 是否 有殘缺
        • 否 , 輸出 CORNER IS OK
        • 是 , 輸出 CORNER NEED TO BE FIXED
          • 若此時庫存冰磚 \(\ge\) 修復所須要的冰磚數
            則 就花費 修復所需冰磚數 個冰磚
          • 不然 庫存冰磚數置零
      5. 輸出: CIRNO FINALLY HAS k ICE_BLOCK(S)
        • k: 剩餘庫存冰磚數
      6. 若上述檢查 的 過程當中
        1. 沒有 一個冰磚 須要被移除
        2. 沒有 一個位置 須要被 填補
        3. 未出現房子沒門的狀況
        4. 房角 的四個柱子 在 建造房頂以前便已修好, 未再進行 修補
        5. 門剛好開在了 某面牆的正中央 (若牆長爲偶數, 則兩中間位置都合法)
        輸出 : CIRNO IS PERFECT!
    對於 \(20\%\) 的數據 : 只有操做 \(1, 2\)
    對於 \(30\%\) 的數據 : 只有操做 \(1, 2, 3\)
    對於 \(50\%\) 的數據 : 只有操做 \(1, 2, 3, 4\)
    對於 \(70\%\) 的數據 : 不會形成任何冰磚摔落 , 也不會形成屋頂塌陷
    對於 \(90\%\) 的數據 : 不把門開到四角的柱子旁邊。
    (保證在全部可能做爲門的牆壁空缺中,有一種可能 使得門 不緊貼四角的柱子)
    對於 \(100\%\) 的數據 :
    \(4\le \text{N}\le 16\ , \ 5\le \text{HM}\le 20 \ ,\ 10\le \text{M}\le 1000\)
    保證 不屬於冰屋範圍內 的 全部空地 至多構成一個連通塊。
    MAKE_ROOF 指令外 , 放置方塊 的 高度小於 \(\text{HM}\)

算法實現 :

分析上述過程當中 較爲複雜的實現 :code

  1. 如何判斷 懸空連通塊, 並將其消除 \(?\)
    能夠 以某連通塊中的 一個冰磚爲起點 , 進行 \(DFS/BFS\)
    轉移時 向相鄰的 冰磚 進行轉移blog

    若是 能夠轉移到 一個 與地面接觸的 冰磚,
    那麼 證實此聯通塊 與地面接觸 , 不懸空
    不然證實 此聯通塊懸空 , 須要 被所有消除
    • 不可 邊判斷邊消除 , 會由於 搜索的 順序 致使錯誤

    消除一個聯通塊時 , 也使用 \(DFS/BFS\) ,
    對轉移到的 冰磚直接消除便可排序

    代碼以下 :

    int DFS1(int R,int C,int H,int FR,int FC,int FH)//dfs 判斷某聯通塊是否懸空 
    {
        vis[R][C][H] = 1;//訪問過,打標記 
        if(H == 0) return 1;//此冰磚 與地面接觸 , 說明此連通塊合法 
        for(int i = 0; i < 6; i ++)//枚舉相鄰的 冰磚 
          if(R + EXX[i] >= 0 && R + EXX[i] <N && C + EXY[i] >= 0) //位置合法 
            if(C + EXY[i] < N && H + EXZ[i] >= 0 && H + EXZ[i] <=HM)
              if(R + EXX[i] != FR || C + EXY[i] != FC || H + EXZ[i] != FH)//不與 上一塊冰磚位置相同 
                if(ice_block[R + EXX[i]][C + EXY[i]][H + EXZ[i]])//有冰磚 
                  if(!vis[R + EXX[i]][C + EXY[i]][H + EXZ[i]])//轉移 
                    if(DFS1(R + EXX[i],C + EXY[i],H + EXZ[i],R,C,H)) return 1;//連通塊 與地面接觸 
        return 0;//懸空 
    }
    int DFS2(int R,int C,int H,int FR,int FC,int FH)//dfs 將某聯通塊 刪除 
    {
        int ret = 0;//計算 刪除的個數 
        for(int i = 0; i < 6; i ++)
          if(R + EXX[i] >= 0 && R + EXX[i] < N && C + EXY[i] >= 0) //枚舉 合法的相鄰冰磚 
            if(C + EXY[i] < N && H + EXZ[i] >= 0 && H + EXZ[i] <= HM)
              if(R + EXX[i] != FR || C + EXY[i] != FC || H + EXZ[i] != FH)
                if(ice_block[R + EXX[i]][C + EXY[i]][H + EXZ[i]])
                  ret += DFS2(R + EXX[i],C + EXY[i],H + EXZ[i],R,C,H);//dfs深刻 
        return ice_block[R][C][H] = 0, ret + 1;//更新冰磚存在狀況,更新刪除個數 
    }
  2. 如何: 表示一門的候選位置相鄰的牆角 , 並計算 牆角的殘缺大小\(?\)

    • 對於 一門的候選位置 相鄰的牆角 ,
      因爲狀況較少 , 暴力枚舉尋找便可

    • 對於一個候選位置 , 其與某牆角關係只有 相鄰/不相鄰 兩種
      考慮 對候選位置與各牆角關係 進行狀壓

      使用一 長度爲\(4\) 的二進制串 , 表示某候選位置 與各牆角關係
      如 : \((1001)_2\) 表示此位置 與牆角\(1\) 與 牆角\(4\) 相鄰

    • 在計算牆角殘缺大小時 , 枚舉 二進制串上對應位置
      根據 門後選位置的高度 進行判斷便可

  3. 如何實現 上述 對門的候選位置 的估價過程 \(?\)

    • 首先找到 全部 離地高度\(\le 2\) 的殘缺位置 ,
      對於每個殘缺位置 , 計算出下列各值 :
      1. 保留此殘缺 對總填補數的影響 (可看到的柱子殘缺數 \(-\) 門候選位置大小)
      2. 此殘缺 的高度
      3. 此殘缺 與牆壁中間位置的距離
    • 按照 分析題意 中規定優先級 進行排序
      排序後的 第一個殘缺 必定使 須要填補冰磚數 最小

    • 若第一個殘缺 高度爲 \(1\), 且所得 須要填補冰磚數 \(<\) 當前庫存
      說明 能夠多消耗 至多一個 冰磚 , 來選擇一個 高度爲 \(2\) 的殘缺做門
      • 能夠 手推 各類類的殘缺 , 來獲得 消耗 至多一個 冰磚 的結論

      則可 在排序後的殘缺中 向後尋找第一個 高度爲 \(2\) 的殘缺
      因爲 第 \(2,3\) 排序優先級的存在 , 找到的第一個 必定爲 最優的


一些技巧

  1. 作題前先寫大綱 必定沒有錯

  2. 多寫子函數 必定沒有錯

  3. \(\text{hack}\) 數據提前聯繫出題人必定沒有錯

  4. 有沒有 什麼方便的方法 能夠 減少調試的難度 ?
    考慮 使全局的 冰磚擺放狀況 可視化

    • 發現 對於肯定的 橫縱座標 , 在此 橫縱座標上的 冰磚 ,
      能夠按照 存在狀況 , 用一個二進制串表示
      即: 對 每個 \(z\) 軸 進行狀壓

      例: \(9 = (1001)_2\) , 表示: 在某位置, 高度爲 \(0,3\) 處 , 分別有一 冰磚
      這樣 狀壓後 輸出 每一個\((x,y)\) 對應的值
      就能夠 在 二維平面內 模擬 三維立體圖形 , 以便於調試

    • 若是您有條件 , 能夠用 \(\text{MC}\) 手玩數據
      配合上述方法使用 效果更佳

    代碼以下 :

    void check()//調試使用, 輸出地圖 , 以檢查合法性 
    {
     for(int x = 0; x < N; putchar('\n'), x ++) 
       for(int y =0; y < N; y ++)
       {
         long long sum = 0;//使用 狀壓思想 , 對每個二維的 座標上的冰磚進行狀壓 
         for(int z = 0; z <= HM; z ++) sum+= (ice_block[x][y][z]) * (1 << z);
         printf("%lld ",sum);
       }
     printf("\n\n");
    }

附完整代碼 :

#include<cstdio>
#include<vector>
#include<algorithm> 
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<string>
#include<ctype.h>
#define min(a,b) (a < b ? a : b)
const int DIR_X[8] = {-1,-1,0,1,1,1,0,-1};//ICE_BARRAGE 操做時的 方向改變量 
const int DIR_Y[8] = {0,-1,-1,-1,0,1,1,1};
const int EXX[6] = {0,0,1,-1,0,0};//立體空間內 相鄰的冰磚 位置改變量 
const int EXY[6] = {0,0,0,0,1,-1};
const int EXZ[6] = {-1,1,0,0,0,0};
//=============================================================
struct alternative_position//門的候選位置 
{
    int x, y, z, h, wall;//位置信息, 
    int corner, corner_pos; //可看到的柱子殘缺數
    int mid1, mid2;//此面牆 中間位置的座標 
};
int N,HM,HR,HC,HX,HY,M;//輸入的各常量 
int inventory;//庫存冰磚數 
bool ice_block[17][17][30];//三維空間內某點 是否有冰磚存在 
int ground_freeze[17][17];//地面冰凍度 ,當地面有冰磚時爲-1 
bool vis[17][17][30];//在dfs求 聯通塊時使用 
int max_high;//牆壁最高高度 
int doorx, doory, doorz, door_high, door_dis;
int corner_x[20], corner_y[20];
//=========子函數列表:==========================================
inline int read()
{
    int s=1, w=0; char ch=getchar();
    for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1;
    for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0';
    return s*w;
}
int abs(int x) {return x > 0? x : -x; }
bool cmp(alternative_position fir, alternative_position sec)
{
    if(fir.corner - fir.h == sec.corner - sec.h)//第一優先級, 消耗冰磚數 升序排序 
    {
      if(fir.h == sec.h)//第二優先級 門的大小 降序排序 
      {
        int minfir = min(abs(fir.wall - fir.mid1), abs(fir.wall - fir.mid2));
        int minsec = min(abs(sec.wall - sec.mid1), abs(sec.wall - sec.mid2));
        return minfir < minsec; //第三優先級, 與牆中央位置的距離 升序排序 
      }
      return fir.h > sec.h;
    }
    return fir.corner - fir.h < sec.corner - sec.h;
}
void ICE_BARRAGE();//操做1,釋放彈幕 
void MAKE_ICE_BLOCK();//操做2,收集冰磚
 
void PUT_ICE_BLOCK();//操做3,放置冰磚 
bool JUDGE_PUT(int R,int C,int H);//判斷 某位置 是否能夠放置冰磚

int DFS1(int R,int C,int H,int FR,int FC,int FH);//dfs 判斷某聯通塊是否懸空 
int DFS2(int R,int C,int H,int FR,int FC,int FH);//dfs 將某聯通塊 刪除 
void REMOVE_ICE_BLOCK();//操做4,移除冰磚 

void MAKE_ROOF(); //操做5, 建造房頂, 移除多餘,修補殘缺,檢查房屋 
int find_max_high();//尋找 牆壁最高處 
void remove_excess(int&,int&);

bool judge_outside(int,int,int);//判斷某冰磚 是否位於 房屋內/外 
bool judge_inside(int,int,int);

bool fill_high_wall(int);//填補 高度>2的牆壁 
int judge_corner(int,int);//判斷 一個位置否開在 牆角,並判斷 開在哪一個牆角 
void corner_could_see(int,int,int,int,int&,int&);//計算一個位置 能看到的牆角的殘缺數 
bool find_door();//在 高度<2的位置 找門 ,並填補非門區域 
void check()//調試使用, 輸出地圖 , 以檢查合法性 
{
    printf("%d \n",inventory); 
    for(int x=0; x<N; putchar('\n'),x++) 
      for(int y=0; y<N; y++)
      {
        long long sum=0;//使用 狀壓思想 , 對每個二維的 座標上的冰磚進行狀壓 
        for(int z=0; z<=HM; z++) sum+= (ice_block[x][y][z]) * (1<<z);
        printf("%lld ",sum);
      }
    printf("\n\n\n");
}
//=============================================================
signed main()//sb main函數 
{
//  freopen("1.in","r",stdin);
//  freopen("koishi.txt","w",stdout);
    N=read(), HM=read(), HR=read(), HC=read(), HX=read(), HY=read(), M=read();
    corner_x[1] = corner_x[2] = HR, corner_x[4] = corner_x[8] = HR + HX - 1;//得到各牆角位置 
    corner_y[2] = corner_y[8] = HC + HY - 1, corner_y[1] = corner_y[4] = HC;
    while(M --)
    {
      std::string order;
      std::cin >> order;
      if(order == "ICE_BARRAGE") ICE_BARRAGE();
      if(order == "MAKE_ICE_BLOCK") MAKE_ICE_BLOCK();
      if(order == "PUT_ICE_BLOCK") PUT_ICE_BLOCK();
      if(order == "REMOVE_ICE_BLOCK") REMOVE_ICE_BLOCK();
      if(order == "MAKE_ROOF") MAKE_ROOF(); 
    }
}
//=============================================================
void ICE_BARRAGE()
{
    int R=read(), C=read(), D=read(), S=read();
    int freeze_num = 0, pass_num = 0;//冰凍度改變的格子數 和 通過的格子數(距離 
    
    for(int x = R, y = C; ;x += DIR_X[D], y += DIR_Y[D])//枚舉射線通過的格子 
    {
      if(x < 0 || x >= N || y < 0 || y >= N || ground_freeze[x][y] == -1) break;//直線不可延伸
      if(ground_freeze[x][y] < 4) ground_freeze[x][y]++ , freeze_num ++;//更新 變量 
      pass_num ++;
      if(pass_num > S) break;//直線不可延伸 
    }
    printf("CIRNO FREEZED %d BLOCK(S)\n",freeze_num);
}
void MAKE_ICE_BLOCK()
{
    int collect_num = 0;//收集 的冰磚數 
    for(int x = 0; x < N; x ++)
      for(int y = 0; y < N; y ++)
        if(ground_freeze[x][y] == 4)//能夠進行收集 
          inventory ++, collect_num ++, //加入庫存 
          ground_freeze[x][y] = 0;//冰凍度清零 
    printf("CIRNO MADE %d ICE BLOCK(S),NOW SHE HAS %d ICE BLOCK(S)\n",collect_num,inventory);
}
bool JUDGE_PUT(int R,int C,int H)//判斷 某位置 是否能夠放置冰磚 
{
    if(ice_block[R][C][H]) return 1;//此位置已被佔用 
    if(H == 0) return 0;//冰磚與地面接觸 
    for(int i = 0; i < 6; i ++) //枚舉立體空間內相鄰的 位置 
      if(R + EXX[i] >= 0 && C + EXY[i] >= 0 && H + EXZ[i] >= 0)
        if(R + EXX[i] < N && C + EXY[i] < N && H + EXZ[i] < HM)
          if(ice_block[R + EXX[i]][C + EXY[i]][H + EXZ[i]]) //有相鄰冰磚 
            return 0; //放置位置合法 
    return 1;//冰磚懸空 
}
void PUT_ICE_BLOCK() 
{
    int R=read(),C=read(),H=read();
    if(! inventory) {printf("CIRNO HAS NO ICE_BLOCK\n"); return ;}//不合法狀況 
    if(JUDGE_PUT(R,C,H)) {printf("BAKA CIRNO,CAN'T PUT HERE\n"); return ;}
    
    ice_block[R][C][H] = 1,inventory --;//放置冰磚 
    if(H == 0) ground_freeze[R][C] = -1;//與地面接觸 ,更改地面冰凍值 
    if(R < HR || R > HR + HX - 1 || C < HC || C > HC + HY - 1) printf("CIRNO MISSED THE PLACE\n");//按照放置位置 輸出 
    else if(HR + 1 <= R && R <= HR + HX - 2 && HC + 1 <= C && C <= HC + HY - 2) printf("CIRNO PUT AN ICE_BLOCK INSIDE THE HOUSE\n");
    else printf("CIRNO SUCCESSFULLY PUT AN ICE_BLOCK,NOW SHE HAS %d ICE_BLOCK(S)\n",inventory);
//  check();
}
int DFS1(int R,int C,int H,int FR,int FC,int FH)//dfs 判斷某聯通塊是否懸空 
{
    vis[R][C][H] = 1;//訪問過,打標記 
    if(H == 0) return 1;//此冰磚 與地面接觸 , 說明此連通塊合法 
    for(int i = 0; i < 6; i ++)//枚舉相鄰的 冰磚 
      if(R + EXX[i] >= 0 && R + EXX[i] <N && C + EXY[i] >= 0) //位置合法 
        if(C + EXY[i] < N && H + EXZ[i] >= 0 && H + EXZ[i] <=HM)
          if(R + EXX[i] != FR || C + EXY[i] != FC || H + EXZ[i] != FH)//不與 上一塊冰磚位置相同 
            if(ice_block[R + EXX[i]][C + EXY[i]][H + EXZ[i]])//有冰磚 
              if(!vis[R + EXX[i]][C + EXY[i]][H + EXZ[i]])//轉移 
                if(DFS1(R + EXX[i],C + EXY[i],H + EXZ[i],R,C,H)) return 1;//連通塊 與地面接觸 
    return 0;//懸空 
}
int DFS2(int R,int C,int H,int FR,int FC,int FH)//dfs 將某聯通塊 刪除 
{
    int ret = 0;//計算 刪除的個數 
    for(int i = 0; i < 6; i ++)
      if(R + EXX[i] >= 0 && R + EXX[i] < N && C + EXY[i] >= 0) //枚舉 合法的相鄰冰磚 
        if(C + EXY[i] < N && H + EXZ[i] >= 0 && H + EXZ[i] <= HM)
          if(R + EXX[i] != FR || C + EXY[i] != FC || H + EXZ[i] != FH)
            if(ice_block[R + EXX[i]][C + EXY[i]][H + EXZ[i]])
              ret += DFS2(R + EXX[i],C + EXY[i],H + EXZ[i],R,C,H);//dfs深刻 
    return ice_block[R][C][H] = 0, ret + 1;//更新冰磚存在狀況,更新刪除個數 
}
void REMOVE_ICE_BLOCK()
{
    int R = read(), C = read(), H = read(); 
    if(!ice_block[R][C][H]) {printf("BAKA CIRNO,THERE IS NO ICE_BLOCK\n"); return ;}//不合法狀況
    
    ice_block[R][C][H] = 0, inventory ++;//移除冰磚, 並 加入庫存 
    if(H == 0) ground_freeze[R][C] = 0;//將地面 冰磚移走,冰凍度置零 
    
    int sum_break = 0;//碎掉冰磚個數 
    for(int i = 0; i < 6; i ++)//枚舉被刪除位置 相鄰的 存在的 冰磚 
      if(R + EXX[i] >= 0 && R + EXX[i] < N && C + EXY[i] >= 0) 
        if(C + EXY[i] < N && H + EXZ[i] >= 0 && H + EXZ[i] <= HM)
          if(ice_block[R + EXX[i]][C + EXY[i]][H + EXZ[i]]) 
          {
            memset(vis,0,sizeof(vis));//dfs判斷 相鄰冰磚 連通塊是否與 地面接觸 
            if(!DFS1(R + EXX[i],C + EXY[i],H + EXZ[i],-1,-1,-1))
              sum_break += DFS2(R + EXX[i],C + EXY[i],H + EXZ[i],-1,-1,-1); 
          }
    
    printf("CIRNO REMOVED AN ICE_BLOCK");//按狀況輸出 
    if(sum_break) printf(",AND %d BLOCK(S) ARE BROKEN",sum_break);
    printf("\n");
}
void MAKE_ROOF()
{
    int remove = 0, filled = 0, corner_filled = 0;//判斷 是否移除多餘 , 填補殘缺 , 牆角殘缺 
    max_high = find_max_high() + 1;//找到 牆壁最高高度 
    //搭建 房頂:
    {
        for(int x = HR; x < HR + HX; x ++)//枚舉 最高高度處 全部位置 
          for(int y = HC; y < HC + HY; y ++)
          {
            inventory -= (!ice_block[x][y][max_high]);//更新
            ice_block[x][y][max_high] = 1;
          }
        if(inventory < 0) {printf("SORRY CIRNO,NOT ENOUGH ICE_BLOCK(S) TO MAKE ROOF\n"); return ;}//不合法狀況 
        if(max_high < 2 || (HX - 2)*(HY - 2)*max_high < 2) {printf("SORRY CIRNO,HOUSE IS TOO SMALL\n"); return ;}
    }
    //移除 多餘冰磚:
    {
        int inside = 0, outside = 0;
        remove_excess(outside,inside);// 移除多餘 
        remove = (inside || outside);//判斷是否 移除 
        
        printf("%d ICE_BLOCK(S) INSIDE THE HOUSE NEED TO BE REMOVED\n",inside);//按狀況輸出 
        printf("%d ICE_BLOCK(S) OUTSIDE THE HOUSE NEED TO BE REMOVED\n",outside);
        if(!DFS1(HR + 1,HC + 1,max_high,-1,-1,-1)) {printf("SORRY CIRNO,HOUSE IS BROKEN WHEN REMOVING BLOCKS\n"); return ;}//判斷 屋頂連通塊是否懸空 
    }
    //填補 牆壁殘缺:
    {
        filled = fill_high_wall(max_high);//填補 高度>=2處 
        filled += find_door();//在高度 <2處尋找門,並填補 
        if(inventory < 0) {printf("SORRY CIRNO,NOT ENOUGH ICE_BLOCKS TO FIX THE WALL\n"); return ;}//不合法狀況 
    }
    //檢查小屋: 
    {
        printf("GOOD JOB CIRNO,SUCCESSFULLY BUILT THE HOUSE\n");
        if(door_high == 2) printf("DOOR IS OK\n");//有 高爲2的門 
        else inventory += (2 - door_high), printf("HOUSE HAS NO DOOR\n");//移除冰塊建門,庫存增長 
        
        if(filled) printf("WALL NEED TO BE FIXED\n");//填補過殘缺 
        else printf("WALL IS OK\n");
        
        for(int z = 0; z <= max_high; z ++) //判斷 牆角的完整性 
          for(int x = HR; x < HR + HX; x += HX - 1)
            for(int y = HC; y < HC + HY; y += HY - 1)
              if(!ice_block[x][y][z]) corner_filled = 1, inventory --;
        if(corner_filled) printf("CORNER NEED TO BE FIXED\n");
        else printf("CORNER IS OK\n");
        
        printf("CIRNO FINALLY HAS %d ICE_BLOCK(S)\n",inventory >= 0?inventory:0);//最後的冰磚數 (若修復過牆角,會致使 庫存<=0,此時還會再製造冰磚並補齊空缺 
        if((!remove) && (!filled) && door_high == 2 && (!corner_filled) && (!door_dis)) //不愧是琪露諾! 輕易就作到了咱們作不到的事
          printf("CIRNO IS PERFECT!");
    }
}
//MAKE_ROOF 子操做 
int find_max_high()//尋找 牆壁最高處 
{ 
    for(int i = HM; i >= 0; i --)//自上往下枚舉 高度 
    {
      for(int j = HC; j < HC + HY; j ++)//枚舉每一面牆 
        if(ice_block[HR][j][i]) return i;
      for(int j = HC; j < HC + HY; j ++)
        if(ice_block[HR + HX - 1][j][i]) return i;
      for(int j = HR; j < HR + HX; j ++)
        if(ice_block[j][HC][i]) return i;
      for(int j = HR; j < HR + HX; j ++)
        if(ice_block[j][HC + HY - 1][i]) return i;
    }
    return -1; 
}
bool judge_outside(int x,int y,int z)//判斷 某位置 是否在房外 
{
    if(HR <= x && x < HR + HX)
      if(HC <= y && y < HC + HY)
        if(z <= max_high) return 0;
    return 1;
}
bool judge_inside(int x,int y,int z)//判斷 某位置 是否在房內 
{
    if(HR < x && x < HR + HX - 1)
      if(HC < y && y < HC + HY - 1)
        if(z < max_high) return 1;
    return 0;
}
void remove_excess(int &outside,int &inside)//移除 多餘方塊 
{
    for(int x = 0; x < N; x ++)//枚舉 立體空間內 每個位置 
      for(int y = 0; y < N; y ++)
        for(int z = 0; z <= HM; z ++)
          if(ice_block[x][y][z])//存在冰塊 
          {
            if(judge_outside(x,y,z)) inventory ++, outside ++, ice_block[x][y][z] = 0;//在屋外 
            if(judge_inside(x,y,z)) inventory ++, inside ++, ice_block[x][y][z] = 0;//在屋內 
            if(!ice_block[x][y][z])//冰塊被移除後 
              for(int i = 0; i < 6; i ++)
                if(x + EXX[i] >= 0 && x + EXX[i] < N && y + EXY[i] >= 0) //枚舉相鄰合法位置的 的 存在的 冰磚 
                  if(y + EXY[i] < N && z + EXZ[i] >= 0 && z + EXZ[i] <= HM)
                    if(ice_block[x + EXX[i]][y + EXY[i]][z + EXZ[i]])
                      if(!judge_outside(x + EXX[i],y + EXY[i],z + EXZ[i]) && (!judge_inside(x + EXX[i],y + EXY[i],z + EXZ[i])))//若是在 牆壁上 
                        {
                          memset(vis,0,sizeof(vis));    
                          if(!DFS1(x + EXX[i],y + EXY[i],z + EXZ[i],-1,-1,-1)) //若是 移除此位置後 牆壁上的 冰磚會摔碎 
                            inventory ++, ice_block[x + EXX[i]][y + EXY[i]][z + EXZ[i]] = 0;//將其移除 ,加入庫存 
                        }
          }
}
bool fill_high_wall(int max_high)//填補 高度 >= 2處的牆 
{
    bool filled = 0;
    for(int x = HR; x < HR + HX; x ++)//枚舉每個位置 
      for(int y = HC; y < HC + HY; y ++)
        for(int z = 2; z <= max_high; z ++)
          if(!judge_outside(x,y,z))//判斷 在牆上 
            if(!judge_inside(x,y,z))
              if(!ice_block[x][y][z]) 
              {
                if(x == HR && y == HC) continue;//在牆角,則 跳過 
                if(x == HR && y == HC + HY - 1) continue;
                if(x == HR + HX - 1 && y == HC) continue;
                if(x == HR + HX - 1 && y== HC + HY - 1) continue;
                inventory --, filled = 1, ice_block[x][y][z] = 1;//填補 
              }
    return filled;//返回 是否填補過 
}
int judge_corner(int x,int y)//判斷位置 (x,y) 是否與牆角相鄰
{
    int ret = 0; //表示 一門的候選位置相鄰的牆角 的 狀態 
    if(x == HR)//枚舉並判斷 ,  
    {
      if(y == HC + 1) ret += 1;
      else if(y == HC + HY - 2) ret += 2;
    }
    if(x == HR + 1)
    {
      if(y == HC) ret += 1;
      else if(y == HC + HY - 1) ret += 2;
    }
    if(x == HR + HX - 2)
    {
      if(y == HC) ret += 4;
      else if(y == HC + HY - 1) ret += 8;
    }
    if(x == HR + HX - 1)
    {
      if(y == HC + 1) ret += 4;
      else if(y == HC + HY - 2) ret += 8;
    }
    return ret;
}
void corner_could_see(int x,int y,int z,int h,int &corner,int &corner_pos)//計算一個位置(x,y,z) 能看到的牆角的殘缺數 
{
    corner_pos = judge_corner(x, y);//判斷 是否哪一個牆角, 並得到 此位置相鄰的牆角 的 狀態  
    corner = 0;
    if(!corner_pos) return;//不與牆角相鄰 
    for(int i = 0; i < 4; i ++)//枚舉牆角 
      if((1 << i )& corner_pos)
      {
        int x1 = corner_x[1 << i], y1 = corner_y[1 << i];//計算殘缺 
        corner += (!ice_block[x1][y1][z]) + (!ice_block[x1][y1][!z])*(h == 2);
      }
} 
bool find_door()//在 高度<2的位置 找門 ,並填補非門區域
{
    std:: vector <alternative_position> pos;//門的候選位置 
    int incomplete = 0, filled_sum = 0, corner_filled = 0;//殘缺數, 填補數, 牆角位置的填補數 
    int midX1 = (HX + 1) /2 + HR - 1, midX2 = (HX + 2) /2 + HR - 1;//兩牆壁中間位置 
    int midY1 = (HY + 1) /2 + HC - 1, midY2 = (HY + 2) /2 + HC - 1;

    for(int x = HR + 1, corner, corner_pos; x < HR + HX - 1; x ++)//枚舉 牆上位置 
      for(int y = HC; y < HC + HY; y += HY - 1)
        if(!ice_block[x][y][0])//根據 殘缺的存在狀況 進行判斷 ,並加入候選位置 
        {
          int z = 0, h = 1 + (!ice_block[x][y][1]);  incomplete += h;
          corner_could_see(x,y,z,h,corner,corner_pos);
          pos.push_back((alternative_position) {x,y,0,h,x,corner,corner_pos,midX1,midX2});
        }
        else if(!ice_block[x][y][1])
        {
          corner_could_see(x,y,1,1,corner,corner_pos);  incomplete += 1;
          pos.push_back((alternative_position) {x,y,1,1,x,corner,corner_pos,midX1,midX2});
        }
        
    for(int y = HC + 1, corner, corner_pos; y < HC + HY - 1; y ++)//枚舉 牆上位置 
      for(int x = HR; x < HR + HX; x += HX - 1)
        if(!ice_block[x][y][0])//根據 殘缺的存在狀況 進行判斷 ,並加入候選位置 
        {
          int z = 0, h = 1 + (!ice_block[x][y][1]); incomplete += h;
          corner_could_see(x,y,z,h,corner,corner_pos);
          pos.push_back((alternative_position) {x,y,0,h,y,corner,corner_pos,midY1,midY2});
        }
        else if(!ice_block[x][y][1])
        {
          corner_could_see(x,y,1,1,corner,corner_pos); incomplete += 1;
          pos.push_back((alternative_position) {x,y,1,1,y,corner,corner_pos,midY1,midY2});
        }
    
    std::sort(pos.begin(), pos.end(), cmp);//排序估值 
    if(pos.size()) //如有候選位置 能夠選擇 ,選擇 最優的 位置 
    {
      filled_sum = incomplete - pos[0].h + pos[0].corner; 
      doorx = pos[0].x, doory = pos[0].y, doorz =  pos[0].z, door_high =pos[0].h;
      door_dis = min(abs(pos[0].wall - pos[0].mid1), abs(pos[0].wall - pos[0].mid2));
    }
    if(door_high == 1 && filled_sum < inventory) //已選擇的殘缺大小爲1, 庫存冰塊足夠選擇更大的殘缺 
      for(int i = 1, size = pos.size(); i < size; i ++)//找到 最優的 高度爲2的殘缺 
        if(pos[i].h == 2) 
        {
          filled_sum = incomplete - pos[i].h + pos[i].corner;
          doorx = pos[i].x, doory = pos[i].y, doorz =  pos[i].z, door_high = pos[i].h;
          door_dis = min(abs(pos[i].wall - pos[i].mid1), abs(pos[i].wall - pos[i].mid2));
          break;
        }
    inventory -= filled_sum;//更新庫存 
    
    for(int i = 0, size = pos.size(); i < size; i ++)//填補 非門空缺 
      if(pos[i].x != doorx && pos[i].y != doory && pos[i].z != doorz)//空缺 不爲門 
      {
        int x = pos[i].x, y = pos[i].y, z = pos[i].z;
        if(pos[i].h == 2) ice_block[x][y][1] = 1;
        ice_block[x][y][z] = 1;
      }
      else if(pos[i].x == doorx && pos[i].y == doory && pos[i].z == doorz && pos[i].corner_pos)
      {
        for(int j = 0; j < 4; j ++) //空缺爲門 , 填補 能夠看到的牆角空缺 
          if((1 << j) & pos[i].corner_pos)//枚舉牆角 
          {
            int x = corner_x[1 << j], y = corner_y[1 << j];//計算答案 
            corner_filled += (!ice_block[x][y][doorz]);
            ice_block[x][y][doorz] = 1;
            if(door_high == 2) corner_filled +=(!ice_block[x][y][!doorz]), ice_block[x][y][!doorz] = 1;
          }
      }
    return filled_sum;
}

最後無良宣傳一下博客 \(wwwwww\)
文章列表 - 地靈殿 - 洛谷博客

\(\text{Updata on 2019.10.19 :}\)

數據增強後 原題解被卡掉了= =
修改了 對在門與柱子相鄰時 對柱子殘缺的判斷
添加了 算法實現

相關文章
相關標籤/搜索