0分求解

P1219 [USACO1.5] 八皇后 Checker Challenge

Little_Fish_Is_Happy @ 2023-12-19 20:04:49

#include <bits/stdc++.h>
using namespace std;
int cnt,n;
int a[100];
bool h[100];
bool l[100];
bool lx[200];
bool ly[200];
void dfs(int t){
    if(t==n+1){
        cnt++;
        if(cnt<=3){
            for(int i=1;i<=n;i++)
            cout<<a[i]<<" ";
            cout<<endl;
        }
        return ;
    }
    for(int i=1;i<=n;i++) {
        if( !h[t] && !l[i] && !lx[t-i+10] && !ly[t+i] ){
            a[t]=i;
            h[t]=true;
            l[i]=true;
            lx[t-i+10]=true;
            ly[t+i]=true;
            dfs(t+1) ;
            h[t]=false;
            l[i]=false;
            lx[t-i+10]=false;
            ly[t+i]=false;
        }
    }
}
int main(){
    dfs(1);
    cin>>n;
    cout<<cnt;
    return 0;
}

哪里错了,帮帮我


by zuanshijian @ 2023-12-19 20:13:54

八皇后是一个经典的回溯法练习题。可以扩展到n*n的棋盘。

常用的解法是二维数组,但是超级慢。我们可以另辟蹊径:一个棋盘的格子只有两种可能:0或1。现在要满足,每一条横行竖列对角线至多一个1,其它都为0。既然这样,我们为什么不想想整数呢?整数是32个0和1的位组成的,刚好可以表示每一个状态。

基本思路还是这样,从上往下进行,一行只放一颗棋子,然后再检查它对竖列和对角线的影响。只不过,要用位运算表达出来。比如,某一行的状态是00001000(也就是整数8),那么下一行的禁止位置(用x表示)就包括:000x0000(左对角线,整数16),0000x000(竖列,整数8),00000x00(右对角线,整数4)三个。这是基本情况。

另外,影响下一行的不只是上一行,还有上两行,三行...等等等等。那么我们看一下,00001000对下两行的禁止位置,分别是00x00000,0000x000,000000x0这三个。

所以,可以这样设计。如果已知某一行的左对角线禁止位置为l,竖列禁止位置为d,右对角线禁止位置为r,这一行放棋子的状态为s,则有下列位运算表达式:

本行禁止位置(记为forbid)为:forbid = l | d | r

下一行左对角线禁止位置:(l | s) << 1

下一行竖列禁止位置:d | s

下一行右对角线禁止位置:(r | s) >> 1

某个状态s可放棋子:(forbid | s) != forbid

这样,运行速度可以达到常规数组法的4-10倍。如果用数组法TLE的童鞋不妨试试这个方法。

另外,根据实际测试,这个程序能跑动的最大n就是15。所以,用32位整数是足够的。

特别需要注意的是,C语言的右移运算有“算术右移”和“逻辑右移”的区别,前者是高位补符号位,后者是高位补0。我们需要的正是后者,但不同编译器对有符号数右移的解释并不相同。所以为了避免这个bug,表示状态的整数应该为unsigned。

最后贴一下AC代码:


by yujiahaoa @ 2024-01-07 07:57:57

@zuanshijian 最后贴一下AC代码暴露了(doge)


|