題意:給定N*M的矩陣,裏面有些有樹,有些沒樹(用0表示),問有多少種方法,通過任意個不交叉環路把樹全連起來(就是哈密頓迴路或者多個哈密頓迴路覆蓋所有非0數)
題解:插頭DP入門題,實際上,感覺插頭DP也就是狀態壓縮DP的一種特例而已。若要學插頭DP,請參見《基於連通性狀態壓縮的動態規劃問題》,看了它,也就知道一些基本概念和解題思路了。
這道題我的方法是dp[i][j][st]代表第i行格子,進行到第j個豎線(該格子左方),輪廓線插頭狀態爲st時的狀態數(1代表有插頭,0代表沒插頭)。
當map[i][j+1]爲0時,看st對應的兩個插頭是否爲1,因爲不能經過(i,j+1)這個格子,所以有插頭的狀態均淘汰掉,可行狀態則直接轉移,dp[i][j+1][st]+=dp[i][j][st]
當map[i][j+1]不爲0時,看st的兩個插頭:
1、都是1,這個格子的插頭狀況也就唯一確定了,直接推出dp[i][j+1][st-a-b]+=dp[i][j][st];(a,b爲相關插頭的二進制編碼)
2、都是0,由於迴路經過的格子必有兩個插頭,這個格子的插頭也就確定了,dp[i][j+1][st|a|b]+=dp[i][j][st];
3、只有一個1,那麼轉移就有兩種可能,向左或者向下,即dp[i][j+1][(st&(~a))|b]+=dp[i][j][st];dp[i][j+1][(st&(~b))|a]+=dp[i][j][st];
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
int map[13][13],n,m;
LL dp[13][13][(1<<13)+1];
int main()
{
int T,ca=0;
for(scanf("%d",&T);T;T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&map[i][j]);
memset(dp,0,sizeof(dp));
dp[0][m][0]=1;
int tot=1<<(m+1),haf=1<<m;
for(int i=1;i<=n;i++)
{
for(int tp=0;tp<haf;tp++)
dp[i][0][tp]=dp[i-1][m][tp<<1];
for(int j=0;j<m;j++)
{
for(int st=0;st<tot;st++)
{
if(!dp[i][j][st])
continue;
int x=i,y=j+1,a=1<<(m-y),b=1<<(m-y+1);
if(map[x][y]==0)
{
if((st&a)||(st&b))
continue;
dp[i][j+1][st]+=dp[i][j][st];
}
else if((a&st)||(b&st))
{
if((a&st)&&(b&st))
dp[i][j+1][st-a-b]+=dp[i][j][st];
else
{
dp[i][j+1][(st&(~a))|b]+=dp[i][j][st];
dp[i][j+1][(st&(~b))|a]+=dp[i][j][st];
}
}
else
{
dp[i][j+1][st|a|b]+=dp[i][j][st];
}
}
}
}
printf("Case %d: There are %I64d ways to eat the trees.\n",++ca,dp[n][m][0]);
}
return 0;
}