這是一篇,以蒟蒻視角展開的梳理總結。更改了一些順序,變化了一些細節。方便蒟蒻學習理解(起碼本蒟蒻是這樣)。大佬們能夠直接看其它大佬的博客,能夠學的更快。php
你必需要學會的前置知識:狀態壓縮DP
學不會依舊能夠讀,可是推薦學的前置知識:哈希html
論文貢前面,建議讀完博客再看。
《基於連通性狀態壓縮的動態規劃問題》ios
很顯然,是一個關於插頭的動態規劃。那麼,什麼是插頭呢?ide
如圖咱們在一個方格內,關於格點畫一條閉合迴路。
對於每個方格,內部,有六種狀況
不難發現,對於迴路裏的任何一個方格,四條邊中,有且僅有兩個與表示路徑的藍色線相交。這也很好理解,進一次,出一次,C(4,2)=6。
咱們如今把格子裏的藍色線條,變成從格子中心指向外邊的→。
這個箭頭,也就是所謂的插頭。學習
咱們結合一個例題來看,這個題目是洛谷模板題的弱化版,不少博客放在了模板題後的第一題,結合我的經歷我以爲它比模板題更適合做第一題。
題目連接:HDU1693 or 洛谷P5074
題目大意:給一個n*m的方格,有些格子必須走,有些格子不能走。問有多少種不一樣的閉合迴路。(1<=n,m<=12)spa
那麼,把迴路模型變成插頭模型有什麼好處或者性質呢?
1:首先,咱們能夠發現,若是一個格子上方的格子有下插頭,那這個格子必定有上插頭。其它方向相似。
2:一個格子的合理取法合且僅合相鄰的格子有關。3d
觀察下第二點,它其實表明了無後效性。假設咱們從上到下,從左到右的處理每個格子,那麼咱們只須要記錄部分格子的狀態便可,再往上的格子具體狀態不用知道。code
如上圖,對於當前格子,咱們只須要知道紅色的這些格子就好了,再上面的格子具體的取法,已經不會對下面任何未處理的格子產生影響。htm
已經掌握了狀態壓縮的你,必定能輕鬆的算出狀態總數,每一個格子6種,維護n個格子。總共6 n 6^n6n種狀態,好的,完蛋,只有2e9個狀態。
別急,咱們真的須要2e9個狀態嘛?這些格子裏,指向彼此和已經處理過的格子的插頭,顯然是廢物信息。咱們實際上只須要知道這些插頭嘛:
藍色的是其它格子須要用到的,黃色的是當前格子須要用到的。咱們只須要知道這m+1個箭頭是否存在就能夠了。總共2 m ∗ 2 2^m*22m∗2個狀態。再乘上n和m,時空複雜度都綽綽有餘。
那麼,怎麼實現呢?咱們要解決兩個問題。blog
1:已知這些插頭的狀況下,這個方格該如何填。
2:填完這個方格後,如何獲得下一個方格所須要的插頭狀態,更特殊的,如何從上一行行末,變到下一行行初。
這兩個問題,其實都不是很難,稍微思考下,均可以獨立解答,建議思考後再往下看。圖片擋下文大法。
問題1:
1:若是當前格子存在左側插頭和上方插頭,那麼只有一種合理填法。
2:若是僅存在左側插頭,那麼有兩種合理填法。
3:若是僅存在上方插頭,那和上一種相似,也是兩種填法。
4:若是都不存在呢?只有一種填法
問題2:
解答了問題1,顯然咱們也獲得了問題2的解答,畢竟咱們填出了這個格子,天然知道插頭分佈。惟一特殊的是上一行末到這一行頭的處理。上一行末不可能有右插頭,那咱們直接把上一行末狀態的表示最後是否存在右插頭的位置去掉,再添加一個表示沒有左插頭的位,不就表示出了這一行第一個的狀態了嘛,爲了方便寫,下方的代碼裏,我用dp[i][0][mask]表示轉移後的上一行行末狀態。
到這裏,咱們已經獲得瞭解法了,成熟的評測機,應該自動AC了吧(劃去)。插頭DP仍是要多寫的,千萬本身寫一遍,別忘了,這只是模板題的弱化。
這裏提供一份代碼(洛谷AC)
1 #include<iostream> 2 #include<stdio.h> 3 #include<cstring> 4 using namespace std; 5 int n,m,maxk,a[13][13]; 6 long long dp[13][13][1<<14]; 7 void init() 8 { 9 scanf("%d%d",&n,&m); 10 maxk=(1<<(m+1))-1; 11 for (int i=1;i<=n;i++) 12 { 13 for (int j=1;j<=m;j++) 14 { 15 scanf("%d",&a[i][j]); 16 } 17 } 18 memset(dp,0,sizeof(dp)); 19 } 20 void solve() 21 { 22 int prei,prej; 23 dp[0][m][0]=1; 24 for (int i=1;i<=n;i++) 25 { 26 for (int k=0;k<=maxk;k++) 27 { 28 dp[i][0][k<<1]=dp[i-1][m][k]; 29 } 30 for (int j=1;j<=m;j++) 31 { 32 prei=i; 33 prej=j-1; 34 for (int k=0;k<=maxk;k++) 35 { 36 int b1=(k>>(j-1))&1; 37 int b2=(k>>j)&1; 38 if (!a[i][j]) 39 { 40 if (!b1&&!b2) dp[i][j][k]+=dp[prei][prej][k]; 41 } 42 else if (!b1&&!b2) 43 { 44 dp[i][j][k+(1<<j)+(1<<(j-1))]+=dp[prei][prej][k]; 45 } 46 else if (b1&&!b2) 47 { 48 dp[i][j][k]+=dp[prei][prej][k]; 49 dp[i][j][k+(1<<(j-1))]+=dp[prei][prej][k]; 50 } 51 else if (!b1&&b2) 52 { 53 dp[i][j][k]+=dp[prei][prej][k]; 54 dp[i][j][k-(1<<(j-1))]+=dp[prei][prej][k]; 55 } 56 else if (b1&&b2) 57 { 58 dp[i][j][k-(1<<j)-(1<<(j-1))]+=dp[prei][prej][k]; 59 } 60 } 61 } 62 } 63 printf("%lld\n",dp[n][m][0]); 64 } 65 int main() 66 { 67 int t; 68 scanf("%d",&t); 69 while (t--) 70 { 71 init(); 72 solve(); 73 } 74 return 0; 75 }