題目連結:
http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=108&page=show_problem&problem=580
題目類型: 暴力, 回溯法
題目:
In chess, the rook is a piece that can move any number of squares vertically or horizontally. In this problem we will consider small chess boards (at most 4
4) that can also contain walls through which rooks cannot move. The goal is to place as many rooks on a board as possible so that no two can capture each other. A configuration of rooks is legalprovided that no two rooks are on the same horizontal row or vertical column unless there is at least one wall separating them.
The following image shows five pictures of the same board. The first picture is the empty board, the second and third pictures show legal configurations, and the fourth and fifth pictures show illegal configurations. For this board, the maximum number of rooks in a legal configuration is 5; the second picture shows one way to do it, but there are several other ways.
Your task is to write a program that, given a description of a board, calculates the maximum number of rooks that can be placed on the board in a legal configuration.
Sample Input
4.X......XX......2XX.X3.X.X.X.X.3....XX.XX4................0
Sample Output
51524
題意:
在象棋中,“車”是可以在棋盤上沿著縱向或橫向走任意格子的棋子。 在這個問題中,我們假設有一個4*4的小棋盤,
在這個棋盤上麵包含著“牆”,而“車”是不能越過牆的。而我們的目標就是儘可能地放置更多地“車”到這個棋盤上去,使所有
的這些”車“互相不能吃到其它棋子。
在上面幾副圖中給出了幾個範例, 棋盤上,格子全部是黑色的代表是“牆”, 黑色圓形的代表是“車”。
其中第二,三副圖的棋子的放置是正確的,第四,五副圖的棋子是錯誤的,因為那兩個棋子可以互相吃到對方。
分析與總結:
做這題不得不說一下非常非常非常非常經典的、只要是有講回溯法的演算法書都會講到的“八皇后問題”。
“八皇后問題”是一個古老而著名的問題,是回溯演算法的典型例題,具體是:在8X8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。
而這題,區別在於要求變成了任意兩個“車”不能同一行、同一列,但是增加了如果有“牆”隔開的話,就可以在同行或同列。
基本上,就是解決這個問題的方法,也就是解決八皇后問題的方法,只要判斷條件改變一下。所以這個問題稱這題為
類八皇后問題。
解法一:暴力枚舉法
把這個問題轉變為,“從4*4的格子中選擇一個子集”,這個子集表示那些格子放置了棋子,那些格子沒有放置格子,
找到合格、放置棋子數量最多的那麼子集,便是答案了。
對於棋盤上的格子,有兩種狀態:放或者不放。那麼,便很容易想到用0和1來表示狀態。既然是0和1,我們可以進一步
用2進位的來表示。4*4共有16個格子,我們需要一個16位的二進位,
即0000000000000000~1111111111111111, 換算成十進位就是0~2^16-1, 我們可以用一個十進位的數,讓他從0開始,
一直加到2^16-1, 就是遍曆了棋盤所有可能的情況。過程中,只需要根據這個數位二進位狀態來判斷那些格子是有放置
棋子,那些格子是沒有放置格子了。可以另外開一個數組專門來儲存這種狀態。
對於每一個子集,我們需要判斷這個子集的放置是否符合要求。只需要遍曆一邊,對於每個放置的格子判斷它是否和其他
棋子有沒有衝突即可。
詳細代碼:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #define MAXN 6 using namespace std; char map[MAXN][MAXN]; int dir[4][2] = {{0,1},{0,-1},{1,0},{-1,0}}; int status[MAXN*MAXN], Max, n; // 把二進位轉換為狀態 bool change(int x){ int pos = n*n-1; if(x==-1) return false; while(pos>=0){ status[pos] = (x & 1); x >>= 1; --pos; } return true; } // 判斷那個點是否有衝突 bool isConflict(int row, int col){ for(int i=0; i<4; ++i){ int dx = row+dir[i][0]; int dy = col+dir[i][1]; while(dx>=0 && dx<n && dy>=0 && dy<n){ if(map[dx][dy]=='X') break; if(status[dx*n+dy]) return true; dx += dir[i][0]; dy += dir[i][1]; } } return false; } // 判斷這种放置方案是否可行 bool judge(){ for(int i=0; i<n; ++i){ for(int j=0; j<n; ++j){ if(status[i*n+j] && map[i][j]=='X'){ return false; } if(status[i*n+j]){ // 如果有放置棋子,判斷他四個方向是否有放置 if(isConflict(i, j)) return false; } } } return true; } int main(){ #ifdef LOCAL freopen("input.txt","r",stdin); #endif while(~scanf("%d", &n) && n){ for(int i=0; i<n; ++i) scanf("%s", map[i]); int xx = (1<<(n*n))-1; int maxNum = -2147483645; while(change(xx--)){ if(judge()){ int sum = 0; for(int i=0; i<n*n; ++i) if(status[i]) ++sum; if(sum > maxNum) maxNum = sum; } }; printf("%d\n", maxNum); } return 0; }
解法二:遞迴回溯