1. 程式人生 > >【搜索】還是N皇後

【搜索】還是N皇後

會有 便是 [1] https flash number 從表 通過 所在

  先看題才是最重要的:  

  技術分享圖片

  這道題有點難理解,畢竟Code speaks louder than words,所以先亮代碼後說話:

 1 #include<iostream>
 2 using namespace std;
 3 char s[1000];int n,map[1000],mod,ans;
 4 void dfs(int deep,int line,int lr,int rl)
 5 {
 6     if(deep>n)
 7     {
 8         ans++;
 9         return;
10     }
11 int pos=mod&(~(line|lr|rl|map[deep])),p; 12 while(pos) 13 { 14 p=pos&-pos; 15 pos-=p; 16 dfs(deep+1,line+p,(lr+p)<<1,(rl+p)>>1); 17 } 18 } 19 int main() 20 { 21 cin>>n; 22 mod=(1<<n)-1; 23 for(int i=1;i<=n;i++)
24 { 25 cin>>s; 26 for(int j=0;j<n;j++) 27 map[i]=(s[j]==.)+(map[i]<<1); 28 } 29 dfs(1,0,0,0); 30 cout<<ans; 31 return 0; 32 }

  這道題是一道搜索+二進制優化題,其實是八皇後的升級版,這就說明你的前置要求是要回普通的八皇後(不會點這裏),初見此題,小編便鼓起勇氣,without thinking twice就稍加改動提交了一遍原來八皇後的代碼,結果甚是殘忍。

  技術分享圖片

  就算是開O2優化也沒有用,親測無效。那麽就只能換個思路了,怎樣能快一些呢?我們不禁會聯想到二進制和位運算(推薦隔壁Alan_Anders的博客二進制和位運算符,小編懶得再多寫一篇這樣的博客了),我們可以嘗試把這個問題簡單化,只考慮最根本的問題:我們通常判斷一個格子是否可以放置皇後,需要哪些要素呢?這就很顯然了,只要懂國際象棋規則的,就一定知道只要這個位置沒有被其他皇後攻擊到就可以了唄,但是這樣判斷就很麻煩,比較耗時間,如果我們每一次能失去這個位置能否放置皇後的判斷,時間復雜度將會降低不少吧?可這也意味著我們必須達到每一次搜索都能精準的判斷出下一次放置的位置在哪裏,這便有了五個要素的判斷。

  1)這個位置本身題目要求就不能放,這就沒辦法了,只能用二維數組來存了如果是‘ . ’,那麽就存為1,表示不能放,否則為0。1和0你們會想到什麽,這就是二進制,那麽我們是否可以改存成一維數組呢?舉個栗子:

  技術分享圖片

  這樣我們是不是就可以以十進制存儲二進制的方式存下每一行,定義一個數組map,按此圖為例,那麽map[1]=4,也就是(0010)?,這裏默認大家二進制會一些基礎知識。

  【前方高能】

  2)在行上的沖突:這就很容易了,每放完一個皇後之後,只要把當前行號+1就可以了,比如現在在第二行放了一個皇後,那麽下一次放就會在第三行(也就是2+1=3(行))。

  3)在列上的沖突:眾所周知,皇後更攻擊的範圍包括了它所在的這一列,這樣進行操作很簡單,比如在第三列放了一個皇後,那麽這一列永遠也不能放置皇後了。

  那麽說了,這麽多,用什麽來表示行列上的狀態呢?行上就不必多說了,直接遞歸時行數的參數加1就可以了。那麽列呢?也要用二進制,舉個栗子:

  技術分享圖片

  按照上面的圖來說:這次我要在紅色格子上放一次皇後,那麽我下一次放置,哪些地方已經不能放了呢?顯然,如下圖所示的藍色格子和第一行(因此表示行號的參數加1)已經不能放了。

  技術分享圖片技術分享圖片

  那麽藍色格子就會標記為1。下一行的列將會從(0000)?更改成(0100)?。

  4)左上到右下的對角線:我們依舊使用二進制來存儲,如下圖(還是同樣的位置、同樣的圖):

  技術分享圖片

  小編依舊要在紅色格子上放置皇後,那麽下一行哪些格子會因為左上到右下的對角線而不能放呢?如圖所示,藍色格子就一定不能。

  技術分享圖片技術分享圖片

  然後標記為1,於是表示左上到右下的對角線二進制值從(0000)?變到了(0010)?。

  4)右上到左下的對角線:同上,只不過會影響到下一行的左邊而不是右邊,從(0000)?變到了(1000)? 。

  我想這些都應該很好理解吧,這是很簡單易懂的,以這個栗子為栗,整理(0010)?、(1000)?、(0100)?,用異或(符號:|)的方法合並成(1110)?,這時我們能輕易的發現下一行的第四位一定是可以放皇後的,因為第四位是0,可是電腦不斷找0是浪費時間的,但是找1是卻是簡單了,現將(1110)?取反變成(0001)?,那麽又怎麽找1?還記得樹狀數組中有個lowbit(假設有一個數x,那麽x&-x便是lowbit(x)的值)嗎?利用lowbit原理(恕小編不才,不會證明,但只要記住這個函數可以取到一個數的二進制數的最右邊的1),就可以找到1的位置(也就是取反前0的位置),然後遞歸這個位置即可,因為可能下一行有>1個位置可以放皇後,所以要減去這個1,再找下一個1,然後繼續進行遞歸。

  【前方高能】

  問題又來了!這個搜索該怎麽寫?我想你現在一定滿肚子問題,現在小編就來回答一下這些問題,看一看與你現在想的一樣不一樣!

  Q:遞歸參數怎麽寫?有哪些要素組成?

  A:遞歸參數還是比較簡單的,只需要4個參數,分別是行、列。左上到右下的對角線和右上到左下的對角線的狀態。

  Q:這些狀態怎麽表示?

  A:全部使用二進制,1表示當前位置不可放,0表示當前位置可以放。

  Q:這些二進制表示的狀態是怎麽存儲的,一直在說是二進制,到底怎麽判斷?

  A:一直在說二進制是為了好理解,但是我們要存成十進制的數,如(1011)?要存成的數值為13。

  Q:為什麽看到亮出的代碼上會有mod=(1<<n)-1;和int pos=mod&(~(line|lr|rl|map[deep])),p;呢?mod到底是用來幹啥的?

  A:註意:我們的二進制表示數一定只有n位,比如是4*4的棋盤,那麽一定二進制表示出的數只有4位,舉個栗子:當在第一行第一位放置一個皇後之後,那麽下一行右上到左下的二進制表示的數為(10000)?,那麽將會多出去一位,為了保持計算的範圍不變,那麽如果我們用(10000)? &(1111)?,那麽結果將會取兩個數都是1的位,由於這兩個數沒有二進制下相同的1的位,所以全部取0,所以下一行右上到左下的二進制表示的數會變成(0000)?。其中mod就是(1111)?,以n=4為例,自己計算一下便會知道mod=(1111)?

  Q:pos&-pos是什麽鬼?

  A:註意看上面的解釋,這句話可以當做lowbit(pos),實在不理解就只能去惡補樹狀數組了。

  Q:實在不理解dfs(deep+1,line+p,(lr+p)<<1,(rl+p)>>1);,腫麽辦?

  A:那我詳細解釋一下:deep是行號的意思,下一次放置皇後當然會在下一行嘍;那麽line呢?這個參數是用來表示列的狀態的,比如說原來列的狀態是(1001)?(表示1,4列已有皇後放置),現在要在p(0100)?(表示在下一行第二個位置放置皇後) 的位置擺放一個皇後,那麽下一次列的狀態將會是當前的狀態(1001)?+下一次要放皇後的位置(0100)?=(1101)?(從表示1,4列已有皇後放置變成了表示1,2,4列已有皇後放置)嘍;斜著的對角線便是一大難點,為什麽要左移(右移)?這是因為對角線是斜著的,舉個栗子:比如說小編現在這一行表示左上到右下的對角線的二進制表示數為(0010)?,那麽這個紅色方塊內的棋子會通過左上到右下的對角線影響到第三行哪些格子呢?很顯然是第三個位置上的棋子,此時第三行的狀態將從(0000)?改為(0001)?,這有什麽規律呢?當然是第三位的1變成了下一行第四位的1,向右移了1位,因此下一行要向右(向左)移。實在不理解文字描述,可以看下面的flash動畫。

  技術分享圖片技術分享圖片

  //小編自己做的flash動畫,如有錯誤,請大牛指出

  技術分享圖片

【搜索】還是N皇後