题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=1693
题目意思:
给n*m的方格,有些方格有障碍,问有多少种方式能使所有的非障碍点都在某个环上,环可以有多个。
解题思路:
轮廓线dp.
dp[i][j][s]表示到达第i行第j列,轮廓线上插头的状态为s时的总的方案数。
对于当前第j列,状态(bool) s&(1<<j)表示当前格的右插头,(bool) s&(1<<(j-1)表示当前格的下插头。
容易知:
当前格为通行状态时:00<=11 01<=10,01 10<=10,01 11<=00
当前格为障碍状态时:00<=00 其他为0
初始化为dp[0][m][0]=1 ;表示初始状态
上一行的最后一列的状态可以左移一位作为下一行的初始状态。
因为对于每一行的最后一列状态有效状态 K0K1K2..Km Km一定为0,不可能有右插头
对于每一行的开始一列的前一状态 K0K1K2..Km K0一定为0,不可能存在第一格的左插头。
所以可以左移一位,作为下一行的初始状态。(注意下标从左置右为从小到大)
代码:
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<queue>
#define eps 1e-6
#define INF 0x1f1f1f1f
#define PI acos(-1.0)
#define ll __int64
#define lson l,m,(rt<<1)
#define rson m+1,r,(rt<<1)|1
using namespace std;
/*
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
*/
ll dp[15][15][1<<15];
int save[15][15];
int main()
{
int t,n,m;
scanf("%d",&t);
for(int ca=1;ca<=t;ca++)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&save[i][j]);
dp[0][m][0]=1; //初始化状态
for(int i=1;i<=n;i++)
{
for(int j=0;j<(1<<(m+1));j++) //换行的时候,只需将状态左移一位
dp[i][0][j<<1]=dp[i-1][m][j];//从k0k1k2...k(m-1)0 到0k1k2k3...km,只需左移一位
for(int j=1;j<=m;j++)
{
for(int k=0;k<(1<<(m+1));k++)
{
int p=1<<j; //当前格子的右插头 //分别从上一格子的 相对于当前格子的左插头和上插头递推过来
int q=p>>1; //当前格子的下插头
bool x=k&p; //是否有插头,将多位转化为1位
bool y=k&q; //是否有插头
if(save[i][j]) //可通
{
dp[i][j][k]=dp[i][j-1][k^p^q]; //相当于这两位取反 00<=11
//01<=10 10<=01 11<=00
//对于每个状态都有一条路联通,另外01<=01 10<=10
if(x!=y) //新增加的情况 10<=10 01<=10
dp[i][j][k]+=dp[i][j-1][k];
}
else //有障碍物
{
if(x==0&&y==0) //均为0
dp[i][j][k]=dp[i][j-1][k]; //前一格子也为0,0,前面一块可以连通
else
dp[i][j][k]=0; //不为0,对于这种状态只能标志为0
}
}
}
}
printf("Case %d: There are %I64d ways to eat the trees.\n",ca,dp[n][m][0]);
// printf("%d\n",dp[n][m][0]); //轮廓线为0(没有插头)的情况
}
return 0;
}