1. 程式人生 > 其它 >狀壓DP入門題【特殊方格棋盤】(部分格不能放) 題解

狀壓DP入門題【特殊方格棋盤】(部分格不能放) 題解

 

(喵喵喵?

 

 

 

在學完狀壓dp後,有種聽懂了又沒聽懂的感覺。。。第二天才做這題,借鑑了一下題解,在cjh大佬幫助下我終於給整明白了,特此寫下我的第一篇題解。

 

  •  題目&描述 

 

題目描述

在n*n(n≤20)的方格棋盤上放置n 個車,某些格子不能放,求使它們不能互相攻擊的方案總數。

輸入格式

輸入檔案第一行,有兩個數 n 、 m ,n表示方格棋盤大小,m表示不能放的格子數量

下面有m行,每行兩個整數,為不能放的格子的位置。

輸出格式

輸出檔案也只有一行,即得出的方案總數。

樣例

樣例輸入

2 1
1 1

樣例輸出1

1



這個題的意思和八皇后很像,但是他讓dfs找方案,所以dfs別想了(其實因為我是遞迴渣)
想到求方案,所以直接想到DP解決(其實是我剛學完狀壓dp)
所以就要用到狀壓dp
  • 程式碼

狀壓dp肯定都會罷?我就不說狀壓是啥了

別問我為啥直接上程式碼,我的思路和解釋全在程式碼註釋裡,我是邊做邊寫註釋的,所以應該寫的比較清楚。。

我覺得我碼風應該不太差罷。。

程式碼思路來源於此篇題解:(im連結~)

#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cmath>
#include
<cstring> #include<cctype> #include<algorithm> #define ll long long #define re register int #define ull unsigned long long #define mem(x,y) memset(x,y,sizeof(x)) #define sandom signed #define INF (1 << 20)-1 using namespace std; int n,m; int cant[22]; ll f[INF];//cant存某一行不能放棋子的點,f[i]表示在i狀態下的方案數
//因為方案數太多,所以疑心f盛不下 我不太清楚啊,按說2^31應該是夠用的 inline int read(){ int x = 0;char c;bool f = false; while(!isdigit(c = getchar())){ if(c == '-'){ c = getchar(),f = true; break; } } do{ x = (x << 3) + (x << 1) + (c & 15); }while(isdigit(c = getchar())); if(f == true) return -x; return x; } int lowbit(int x){ return x & (-x); } void work(){ n = read(),m = read(); for(re i = 1,hang,lie ; i <= m ; ++ i){ hang = read(),lie = read(); cant[hang] += 1 << (lie - 1);//畫圖 } /* 有一個點我澄清一下,這個狀壓的二進位制表示,因為最低位在最右邊, 所以在真正表示的時候,右邊對應著狀態的起點: 0 1 0 1 (二進位制數) 1 0 1 0 (狀態表示) */ f[0] = 1;//啥都不放也就是{0 0 0 0}的時候,也算一種方案 for(re s = 1 ; s <= ((1 << n) - 1) ; ++ s){//列舉每個狀態 //要明白一個點,我們列舉的是狀態而不是某一行 /* 列舉的狀態就是指的是: {0 0 0 1},{0 0 1 0},{0 0 1 1},{0 1 0 0}...... 以此類推罷,沒錯和你想的一樣,就是所有的放車的狀態都列舉,這也是狀壓dp(n的資料範圍)這麼小的原因 因為要從最初狀態進行遞推,所以這麼著 */ int one_num = 0; for(re i = s ; i > 0 ; i -= lowbit(i)){//取當前狀態的所有的1 ++ one_num; /*對lowbit的解釋::: 學樹狀陣列的時候lowbit就是直接用的不知道原理,這裡我不再忍了,我來說一下lowbit lowbit的作用是從右到左取第一個遇到的1,就比如取{0 1 0 0 1 0}取的是第二位那個1 首先,-x是取反加1,也就是補碼,例子: 原碼:0 1 0 1 0 0 取反:1 0 1 0 1 1 (符號位也變,就是處於最高位那一位(這個描述不太嚴謹,因為我不清楚符號位算不算'位')) +1 :1 0 1 1 0 0 然後將-x和x進行按位與(&)操作 0 1 0 1 0 0 & 1 0 1 1 0 0 ---------------- 0 0 0 1 0 0 所以就得到了從右到左←←←得到的第一個1 */ /*對-=lowbit(i)的解釋::: 那這裡我i-=lowbit(i)是甚麼意思呢? i不是一個狀態嗎,也就是這一行的狀態 所以我就這個one_num操作是通過-=lowbit(i){ 來搞這一行一共有多少個1的 },因為一會要用: 這個one_num表示的是到了第幾行,你想啊,我們是想要這麼幹:每行都放一個,每列不能同時出現兩個及以上,這樣就可以完美地放下 但是由於我們每一個放的位置不相同(既包括第一行放置位置,也包括以後的放置位置),所以會出現多種情況,這也是我們列舉每個狀態的原因 */ } for(re i = s ; i > 0 ; i -= lowbit(i)){ if((cant[one_num] & lowbit(i)) == 0){//找合法的放置位置,即從哪種子狀態哪裡轉移過來 int x = (s ^ (lowbit(i)));//列舉的某一種子狀態 f[s] += f[x];//大狀態的方案數 += 子狀態的方案數 /* 這個東西乍一見實際上是不懂的,但經過cjh大佬的耐心澄清後我理解了,在此說明一下: 首先我們要明確這一步操作的目的,這一步操作是為了{ 找到合法的放置位置 } 進一步由放置位置合不合法就等同於 { 把不合法的子狀態給淘汰掉。 } Q:怎麼淘汰? A:首先我們需要一種子狀態來轉移到當前列舉的狀態s 例如:大狀態{0 1 0 1 1 0};子狀態{0 1 0 1 0 0}、{0 1 0 0 1 0}、{0 0 0 1 1 0}; 所以我們要嘗試每次刨去s中的某一個1,這樣可以覆蓋到(列舉到)每一個子狀態,以此來達到列舉子狀態的目的。 那麼回到原來,這個if裡面啥意思呢? 這個就是判斷當前放置位置是否合法,以下是為什麼這麼判斷是可以的: 例如: 當前列舉的狀態s: 0 1 0 1 1 0 當前表示的狀態i://注意,i表示的不是子狀態,而是刪去某個點 0 1 0 1 0 0 取lowbit後: 0 0 0 1 0 0 cant陣列: 0 0 0 1 0 0 把cant和取lowbit按位與,結果非0,那麼說明當前這個點不能放 那麼如果按位與之後是0,那就說明當前這個點能放,反之不能 淘汰了不合法的放置位置後就可以安心進行轉移了 */ } } } printf("%lld",f[((1 << n) - 1)]);//把這個表示出來: /* n = 6,n: 0 0 0 0 1 1 0 但是我們最後要統計的當然是: 0 1 1 1 1 1 1 (n=6,這種狀態表示的是每一個地方都放好了(一共有n=6列)) 所以就是這樣,1<<n: 0 0 0 0 0 0 1 進位n: 1 0 0 0 0 0 0 再減1: 0 1 1 1 1 1 1 好耶! */ } sandom main(){ work(); return 0; } //Viow Flat

(內個,點贊這種事qwq......