n皇后问题(bitmask 优化)

description:

经典八皇后问题的拓展,有一个n*n的棋盘,问放n个皇后在棋盘中且都相不攻击的种数有多少。


analysis:

大的框架使用回溯搜索法,一行一行放置皇后。但是在确定当前皇后的摆放位置时,考虑两种优化。

第一种优化:

在确定当前行哪些格子可以放时,当然不能每次都重新考虑之前走过的所有皇后对该行产生的影响。一般都是要要采用一种递推的策略,而不是重复计算。

棋盘中放置一个皇后,实际上是确定了一个“米”字型的禁止放置区域,问题在于快速确定当前行中哪些格子在静止放置区域中。既然是采用一行一行的搜索方式,就要找到前一行的禁止放置位置与当前行的禁止放置位置的关系。


如图,注意到米字型的禁止区可以分解为三类 纵向攻击线,右斜向攻击线,和左斜向攻击线。每次考虑下一行实际上都是三类线沿着格子方向的一个衍生。这样就有了一种单调性,维护三个bitset,分别表示当前行中位于三类攻击线上的点的集合,则在下一行中的禁止放置位置就是由上一行的三类攻击线延伸得到。具体来说,假设第i行的三类点的集合分别为,col[i],L[i],R[i].且当前行在第j个位置放皇后,则 col[i+1]=col[i]&(1<<j), R[i+1]=(R[i]|(1<<j))>>1, R[i+1]=(R[i]|(1<<j))<<1.  而第i行的可行位置的集合可以表示为 (1<<n)&(~col[i])&(~L[i])&(~R[i]), 这样就能快速的在O(1)时间内得到可行位置的bitset.

第二种优化:

在得到了可行位置的bitset后,可以快速的找到里面为1的位置,而不是一位一位的枚举看是否等于1. 方法是用S&(-S) 的技巧。假设当前可行位置bitset是S,设S中最低位为1的值为j,则S&(-S)得到的是(1<<j)(这个可用二进制的补码验证,我第一次见到也是在树状数组的讲解里)。这样可以快速的找到S中不为0的最右位置,并且每次可将S右移j位,达到快速依次访问可行位置的目的。

代码:

#include<iostream>
#include<string>
#include<cstring>
#include<vector>
#include<stack>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<sstream>
#include<cmath>
#include<iterator>
#include<bitset>
#include<stdio.h>
#include<windows.h>
using namespace std;
#define _for(i,a,b) for(int i=(a);i<(b);++i)
#define _rep(i,a,b) for(int i=(a);i<=(b);++i)
typedef long long LL;
const int INF = 1 << 30;
const int maxn = 200005;
int n;

//int bitcount(int S){return S?(S&1)+bitcount(S>>1):0;}

void solve(int cur,int col_attack,int r_attack,int l_attack,int & ans){  //当前行,直线集合,右斜线集合,左斜线集合,答案
    if(cur==n) {ans++;return;}

    int ok=((1<<n)-1)&(~col_attack);ok&=~r_attack;ok&=~l_attack;
    while(ok){
        int i=ok&(-ok);
        int ncol=col_attack|i;
        int nr=(r_attack|i)>>1;
        int nl=(l_attack|i)<<1;
        solve(cur+1,ncol,nr,nl,ans);
        ok&=~i;
    }
}

int main()
{

    //freopen("C:\\Users\\admin\\Desktop\\in.txt", "r", stdin);
    //freopen("C:\\Users\\admin\\Desktop\\out.txt", "w", stdout);

    for(n=15;n<=18;++n){
        int ans=0;
        double start=GetTickCount();
        solve(0,0,0,0,ans);
        double end=GetTickCount();
        cout<<n<<" 皇后: "<<ans<<" 时间"<<end-start<<endl;
    }
    return 0;
}

结果:





猜你喜欢

转载自blog.csdn.net/tomandjake_/article/details/80382729
今日推荐