2n皇后問題

在藍橋杯基礎訓練題中,出現這樣一道題目:

問題描述

  給定一個n*n的棋盤,棋盤中有一些位置不能放皇后。現在要向棋盤中放入n個黑皇后和n個白皇后,使任意的兩個黑皇后都不在同一行、同一列或同一條對角線上,任意的兩個白皇后都不在同一行、同一列或同一條對角線上。問總共有多少种放法?n小於等於8。

輸入格式

  輸入的第一行爲一個整數n,表示棋盤的大小。
  接下來n行,每行n個0或1的整數,如果一個整數爲1,表示對應的位置可以放皇后,如果一個整數爲0,表示對應的位置不可以放皇后。

輸出格式

  輸出一個整數,表示總共有多少种放法。

樣例輸入

4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1

樣例輸出

2

樣例輸入

4
1 0 1 1
1 1 1 1
1 1 1 1
1 1 1 1

樣例輸出

0


在解決2n皇后問題前,先來學習目前公認N皇后的最高效算法。

使用位運算來求解N皇后的高效算法

   核心代碼如下:

void test(int row, int ld, int rd)
{
	int pos, p;
	if ( row != upperlim )
	{
		pos = upperlim & (~(row | ld | rd ));
		while ( pos )
		{
			p = pos & (~pos + 1);
			pos = pos - p;
			test(row | p, (ld | p) << 1, (rd | p) >> 1);
		}
	}
	else
		++Ans;
}

        初始化: upperlim =  (1 << n)-1; Ans = 0;

        調用參數:test(0, 0, 0);

         和普通算法一樣,這是一個遞歸函數,程序一行一行地尋找可以放皇后的地方。函數帶三個參數row、ld和rd,分別表示在縱列和兩個對角線方向的限制條件下這一行的哪些地方不能放。位於該行上的衝突位置就用row、ld和rd中的1來表示。把它們三個並起來,得到該行所有的禁位,取反後就得到所有可以放的位置(用pos來表示)。

        p = pos & (~pos + 1)其結果是取出最右邊的那個1。這樣,p就表示該行的某個可以放子的位置,把它從pos中移除並遞歸調用test過程。

        注意遞歸調用時三個參數的變化,每個參數都加上了一個禁位,但兩個對角線方向的禁位對下一行的影響需要平移一位。最後,如果遞歸到某個時候發現row=upperlim了,說明n個皇后全放進去了,找到的解的個數加一。


注:
        upperlime:=(1 << n)-1 就生成了n個1組成的二進制數。
        這個程序是從上向下搜索的。
        pos & -pos 的意思就是取最右邊的 1 再組成二進制數,相當於 pos &(~pos +1),因爲取反以後剛好所有數都是相反的(怎麼聽着像廢話),再加 1 ,就是改變最低位,如果低位的幾個數都是1,加的這個 1 就會進上去,一直進到 0 ,在做與運算就和原數對應的 1 重合了。舉例可以說明:

        原數 0 0 0 0 1 0 0 0    原數 0 1 0 1 0 0 1 1

        取反 1 1 1 1 0 1 1 1    取反 1 0 1 0 1 1 0 0
        加1    1 1 1 1 1 0 0 0    加1  1 0 1 0 1 1 0 1

  與運算    0 0 0 0 1 0 0 0    and  0 0 0 0 0 0 0 1
      其中呢,這個取反再加 1 就是補碼,and 運算 與負數,就是按位和補碼與運算。
       (ld | p)<< 1 是因爲由ld造成的佔位在下一行要右移一下;
       (rd | p)>> 1 是因爲由rd造成的佔位在下一行要左移一下。
        ld rd row 還要和upperlime 與運算 一下,這樣做的結果就是從最低位數起取n個數爲有效位置,原因是在上一次的運算中ld發生了右移,如果不and的話,就會誤把n以外的位置當做有效位。
        pos 已經完成任務了還要減去p 是因爲?
        while 循環是因爲?
        在進行到某一層的搜索時,pos中存儲了所有的可放位置,爲了求出所有解,必須遍歷所有可放的位置,而每走過一個點必須要刪掉它,否則就成死循環啦!

         這個是目前公認N皇后的最高效算法。

(以上內容來源於博客http://blog.csdn.net/hackbuteer1/article/details/6657109

這個算法如此巧妙地解決了n皇后問題。不過,2n皇后問題比此多了兩個限制條件:

1、n*n的棋盤中有黑皇后和白皇后各n個,任意兩個同色皇后不能在同一行、同一列或同一條對角線上,而且同一位置只有一個皇后;

2、棋盤中有數個位置不能放任何皇后(個數和位置隨機);

條件還不算苛刻,由目前公認N皇后的最高效算法稍微改造一下便可以解決這題。

至此,大家可能會有兩個疑問:

1、在每行中,如果兩種皇后可放位置的首選位置衝突時如何解決?能否保證兩種皇后分別放在此位置的情況都統計上?

2、如何篩選掉條件2中的這些禁止位?

/* 
** 目前最快的2N皇后遞歸解決方法 
** 2N Queens Problem 
** 試探-回溯算法,遞歸實現
** 根據http://blog.csdn.net/hackbuteer1/article/details/6657109改編
*/
#include <stdio.h>
#include <stdlib.h>
#define MAXN	32
long sum = 0, upperlim = 1, wall[MAXN] = {0};
// 試探算法從最右邊的列開始。  
void BlackWhiteQueen(int line, long row1, long ld1, long rd1, long row2, long ld2, long rd2)
{
	long pos1, pos2, p1, p2;
	if(row1 != upperlim || row2 != upperlim)
	{
		// row,ld,rd進行“或”運算,求得所有可以放置皇后的列,對應位爲0,  
        // 然後再取反後“與”上全1的數,來求得當前所有可以放置皇后的位置,對應列改爲1  
        // 也就是求取當前哪些列可以放置皇后  
		pos1 = upperlim & ~(row1 | ld1 | rd1) & ~wall[line];
		while(pos1)		// 0 -- 皇后沒有地方可放,回溯  
		{
			// 拷貝pos最右邊爲1的bit,其餘bit置0  
            // 也就是取得可以放皇后的最右邊的列  
			p1 = pos1 & -pos1;
			// 將pos最右邊爲1的bit清零  
            // 也就是爲獲取下一次的最右可用列使用做準備,  
            // 程序將來會回溯到這個位置繼續試探
			pos1 -= p1;
			pos2 = upperlim & ~(row2 | ld2 | rd2) & ~wall[line] & ~p1;
			while(pos2)
			{
				p2 = pos2 & -pos2;
				pos2 -= p2;
				// row + p,將當前列置1,表示記錄這次皇后放置的列。  
	            // (ld + p) << 1,標記當前皇后左邊相鄰的列不允許下一個皇后放置。  
	            // (ld + p) >> 1,標記當前皇后右邊相鄰的列不允許下一個皇后放置。  
	            // 此處的移位操作實際上是記錄對角線上的限制,只是因爲問題都化歸  
	            // 到一行網格上來解決,所以表示爲列的限制就可以了。顯然,隨着移位  
	            // 在每次選擇列之前進行,原來N×N網格中某個已放置的皇后針對其對角線  
	            // 上產生的限制都被記錄下來了  
				BlackWhiteQueen(line + 1, row1 + p1, (ld1 + p1) << 1, (rd1 + p1) >> 1, row2 + p2, (ld2 + p2) << 1, (rd2 + p2) >> 1); 
			}
		}
	}
	else
		sum++;
}//BlackWhiteQueen

int main(int argc, char const *argv[])
{
	int n = 8, i, d;
	scanf("%d", &n);
	// 因爲整型數的限制,最大隻能32位,  
    // 如果想處理N大於32的皇后問題,需要  
    // 用bitset數據結構進行存儲  
	if((n < 1) || (n > 32))
	{
		printf("只能計算1~32之間\n");
		exit(-1);
	}
	// N個皇后只需N位存儲,N列中某列有皇后則對應bit置1。 
	upperlim = (upperlim << n) - 1;
	for (i = 0; i < n * n; ++i)
	{
		scanf("%d", &d);
		if(d == 0)
			wall[i / n] |= 1 << (i % n);
	}
	BlackWhiteQueen(0, 0, 0, 0, 0, 0, 0);
	printf("%d", sum);
	return 0;
}

疑問一對於正確的回溯算法來說,完全不在話下。

需要注意的是,當計算出當前行白皇后所放位置p1後,計算p2時

pos2 = upperlim & ~(row2 | ld2 | rd2) & ~wall[line] & ~p1;

不能寫成

pos2 = (upperlim & ~(row2 | ld2 | rd2) & ~wall[line]) - p1;//或pos2 = (upperlim & ~(row2 | ld2 | rd2)) - wall[line] - p1;

當兩種皇后可放位置的首選位置不同時,後者得出的pos2顯然是錯誤的。

之所以pos1 -= p1;正確是因爲p1中爲‘1’的位pos1對應的位也爲‘1’。而pos2卻不一定。

而對於問題2,在這裏參考了row, ld, rd這三個參數的做法,wall[]數組中,前n個有效,wall[k]的二進制數中爲‘1’的位表示第k行中的這個位置不能放皇后。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章