2n皇后問題

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

問題描述數組

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

輸入格式ide

  輸入的第一行爲一個整數n,表示棋盤的大小。
  接下來n行,每行n個0或1的整數,若是一個整數爲1,表示對應的位置能夠放皇后,若是一個整數爲0,表示對應的位置不能夠放皇后。函數

輸出格式學習

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

樣例輸入.net

4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1orm

樣例輸出blog

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皇后問題比此多了兩個限制條件:

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

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

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

至此,你們可能會有兩個疑問:

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

二、如何篩選掉條件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行中的這個位置不能放皇后。

相關文章
相關標籤/搜索