题意:给定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;
}