1. 程式人生 > >USACO-Section2.1 The Castle [搜尋][深度優先搜尋]

USACO-Section2.1 The Castle [搜尋][深度優先搜尋]

題目大意

我們憨厚的USACO主人公農夫約翰(Farmer John)以無法想象的運氣,在他生日那天收到了一份特別的禮物:一張“幸運愛爾蘭”(一種彩票)。結果這張彩票讓他獲得了這次比賽唯一的獎品——坐落於愛爾蘭郊外的一座夢幻般的城堡!
喜歡吹噓的農夫約翰立刻回到有著吹噓傳統的威斯康辛老家開始吹噓了, 農夫約翰想要告訴他的奶牛們關於他城堡的一切。他需要做一些吹噓前的準備工作:比如說知道城堡有多少個房間,每個房間有多大。另外,農夫約翰想要把一面單獨的牆(指兩個單位間的牆)拆掉以形成一個更大的房間。 你的工作就是幫農夫約翰做以上的準備,算出房間數與房間的大小。
城堡的平面圖被劃分成M*N(1 <=M,N<=50)個正方形的單位,一個這樣的單位可以有0到4面牆環繞。城堡周圍一定有外牆環繞以遮風擋雨。(就是說平面圖的四周一定是牆。)

請仔細研究下面這個有註解的城堡平面圖:

     1   2   3   4   5   6   7
   #############################
 1 #   |   #   |   #   |   |   #
   #####---#####---#---#####---#   
 2 #   #   |   #   #   #   #   #
   #---#####---#####---#####---#
 3 #   |   |   #   #   #   #   #   
   #---#########---#####---#---#
 4 # 
-># | | | | # # #############################

# =牆壁 -,| = 沒有牆壁
-> =指向一面牆,這面牆推掉的話我們就有一間最大的新房間
友情提示,這個城堡的平面圖是7×4個單位的。一個“房間”的是平面圖中一個由“#”、“-”、“|”圍成的格子(就是圖裡面的那一個個的格子)。比如說這個樣例就有5個房間。(大小分別為9、7、3、1、8個單位(排名不分先後))
移去箭頭所指的那面牆,可以使2個房間合為一個新房間,且比移去其他牆所形成的房間都大。
城堡保證至少有2個房間,而且一定有一面牆可以被移走。

格式

INPUT FORMAT:

第一行有兩個整數:M和N 城堡的平面圖用一個由數字組成的矩陣表示,一個數字表示一個單位,矩陣有N行M列。輸入與樣例的圖一致。
每一個單位的數字告訴我們這個單位的東西南北是否有牆存在。每個數字是由以下四個整數的某個或某幾個加起來的(四面都沒有牆的話,這個數字應該為0)。
1: 在西面有牆
2: 在北面有牆
4: 在東面有牆
8: 在南面有牆
城堡內部的牆會被規定兩次。比如說(1,1)南面的牆,亦會被標記為(2,1)北面的牆。

OUTPUT FORMAT:

輸出包含如下4行:
第 1 行: 城堡的房間數目。
第 2 行: 最大的房間的大小
第 3 行: 移除一面牆能得到的最大的房間的大小
第 4 行: 移除哪面牆可以得到面積最大的新房間。
選擇最佳的牆來推倒。有多解時選最靠西的,仍然有多解時選最靠南的。同一格子北邊的牆比東邊的牆更優先。
用該牆的南鄰單位的北牆或西鄰單位的東牆來表示這面牆,方法是輸出鄰近單位的行數、列數和牆的方位(”N”(北)或者”E”(東))。

SAMPLE INPUT

7 4
11 6 11 6 3 10 6
7 9 6 13 5 15 5
1 10 12 7 13 7 5
13 11 10 8 10 12 13

SAMPLE OUTPUT

5
9
16
4 1 E

(copy from nocow)

題解

這題可以分為兩個步驟:
1.搜尋(也可以說是用深搜實現的Flood Fill 演算法),找到所有的房間,並編上號,記錄每個房間的大小。
2.拆牆。列舉所有的正方形格子,如果這個格子的上有一面牆分隔開了兩個不同的房間,則記錄這面牆。找出所有這樣的可以拆的牆,拆最優的那一個。注意拆牆的順序,從西到東,從南至北。

這題給出的資料格式也是很巧秒的,可以利用二進位制的位運算簡化程式碼。
1: 在西面有牆
2: 在北面有牆
4: 在東面有牆
8: 在南面有牆
所以每個正方形格子裡的數字範圍為0~15, 可以用4位的二進位制數表示。1 = 20, 2 = 21, 4 = 22, 8 = 23, 剛好和上面的四種牆對應。判斷某一個格子的四個方向有沒有牆,只需要判斷對應的二進位制位上的數字是0還是1就可以了。
我的程式碼裡 dfs()搜尋和rmWall()拆牆,都用了位運算使得程式碼稍微簡潔一些。

程式碼

#include <iostream>
#include <fstream>
#include <cstring>
#define MAXN 51 
#define cin fin
#define cout fout
using namespace std;
ifstream fin("castle.in");
ofstream fout("castle.out");

int N, M;
int a[MAXN][MAXN];
int size[MAXN*MAXN], num = 0;
int room[MAXN][MAXN];
int fx[] = {0, -1, 0, 1};
int fy[] = {-1, 0, 1, 0};
char dir[] = {'W', 'N', 'E', 'S'};

void dfs(int x, int y, int n) {
    room[x][y] = n;
    size[n]++;
    int bit = 1;
    //位運算 列舉4個方向
    for (int i = 0; i < 4; i++) {
        if (!(bit & a[x][y])) {
            int tx = x + fx[i];
            int ty = y + fy[i];
            if (tx >= 0 && tx < N &&
                    ty >= 0 && ty < M &&
                    !room[tx][ty]) {
                dfs(tx, ty, n);
            }
        }
        bit <<= 1;
    }
}

int rmWall(int &x, int &y, char &op) {
    int tmp = 0;
    //列舉所有的格子,注意列舉的方向,
    for (int j = 0; j < M; j++) { //從西到東
        for (int i = N-1; i >= 0; i--) { //從南至北
            int bit = 2; //從2開始,因為西牆就是西邊相鄰格子的東牆,考慮過了
            for (int l = 1; l < 3; l++) {
                if (bit & a[i][j]) {
                    int tx = i + fx[l];
                    int ty = j + fy[l];
                    if (tx >= 0 && tx < N &&
                            ty >= 0 && ty < M &&
                            room[i][j] != room[tx][ty]) {
                        if (tmp < size[room[i][j]] + size[room[tx][ty]]) {
                            tmp = size[room[i][j]] + size[room[tx][ty]];
                            x = i; y = j; op = dir[l];
                        }
                    }
                }
                bit <<= 1;
            }
        }
    }
    return tmp;
}

int main() {
    cin >> M >> N;
    memset(size, 0, sizeof(size));
    memset(a, 0, sizeof(a));
    memset(room, 0, sizeof(room));
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            cin >> a[i][j];
        }
    }
    //搜尋
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (!room[i][j]) {
                ++num;
                dfs(i, j, num);
            }
        }
    }
    //輸出房間數和最大房間的大小
    cout << num << endl;
    int tmp = size[1];
    for (int i = 2; i <= num ; i++) {
        tmp = max(tmp, size[i]);
    }
    cout << tmp << endl;
    //拆牆
    int i, j;
    char op;
    int ans = rmWall(i, j, op);
    cout << ans << endl;
    cout << i+1 << " " << j+1 << " " << op << endl;
    return 0;
}