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