您的位置:首页 > 其它

UVa 639 - Don't Get Rooked, 类八皇后问题

2012-07-15 01:39 375 查看

639 - Don't
Get Rooked
391859.67%

199790.59%

题目链接:

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..
....
2
XX
.X
3
.X.
X.X
.X.
3
...
.XX
.XX
4
....
....
....
....
0


Sample Output

5
1
5
2
4


题意:

在象棋中,“车”是可以在棋盘上沿着纵向或横向走任意格子的棋子。 在这个问题中,我们假设有一个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;
}


解法二:递归回溯

如果用递归回溯做的话,递归的代码非常简短优雅

,而且最终的运行效率比上面的代码还要高1/3.

用递归回溯左的基本思想是不断地尝试地对每个点放置,如果不符合条件要求的话,就回溯,直到全部

放置好,然后根据这次放置的数量维护一个“最大值”。最终,这个“最大值”就是答案。

#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 maxNum, n;
bool vis[MAXN][MAXN];

// 判断那个点是否有冲突
inline 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(vis[dx][dy]) return true;
            dx += dir[i][0];
            dy += dir[i][1];
        }
    }
    return false; 
}

void dfs(int x, int y, int num){
    for(int i=x; i<n; ++i){
        for(int j=0; j<n; ++j){
            if(!vis[i][j] && map[i][j]=='.' && !isConflict(i, j)){
                vis[i][j] = true;
                dfs(i, j, num+1);
                vis[i][j] = false; // 回溯
            }
        }
    }
    if(num > maxNum) maxNum = num;
}

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]);
        }
        maxNum = -2147483645;
        memset(vis, 0, sizeof(vis));
        dfs(0, 0, 0);
        printf("%d\n", maxNum);
    }
    return 0;
}




—— 生命的意义,在于赋予它意义。


原创 http://blog.csdn.net/shuangde800 , By
D_Double [b](转载请标明)
[/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: