测试地址:Eat the Trees
题目大意:一个N*M的矩形场地,有一些格子不能走,要求走若干条回路使得走过每个格子一次且仅一次,求方案数。
做法:一看到棋盘类型的题目和很小的数据范围就想到插头DP。因为题目中要求若干条回路,所以轮廓线状态定义直接和骨牌覆盖问题一样,插头为1表示这个位置需要一个格子来承接,插头为0表示不需要。设f[i][j][state]为递推到第i行第j列的格子,轮廓线状态为state的方案数,逐格递推,枚举轮廓线状态,并找出第j和第j+1位(从左到右),称作关键位,判断:如果当前格子不能走,那么仅当两个关键位都为0时才更新状态,此时f[i][j][state]=f[i][j-1][state],否则f[i][j][state]=0。如果当前格子能走,按照关键位分成3种情况:两个关键位都为0,两个关键位都为1或者其中一个关键位为0,另一个关键位为1,分情况状态转移即可。和一般的插头DP问题一样,要注意递推到一行最后一个格子时轮廓线状态的转移。
我傻逼的地方:轮廓线状态是m+1位的,所以数组要开到2^(m+1)那么多,而不是2^m......结果我交上去竟然提示TLE而不是RE,很迷......
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,m,T,w[20][20],now,past;
ll f[2][5010];
int main()
{
scanf("%d",&T);
for(int t=1;t<=T;t++)
{
memset(f,0,sizeof(f));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&w[i][j]);
f[0][0]=1;
now=1,past=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
for(int k=0;k<(1<<(m+1));k++)
{
int bit1=k&(1<<(j-1)),bit2=k&(1<<j);
if (!bit1&&!bit2)
{
if (!w[i][j]) f[now][k]=f[past][k];
else f[now][k]=f[past][k+(1<<(j-1))+(1<<j)];
}
else if (!w[i][j]) {f[now][k]=0;continue;}
else if (bit1&&bit2) f[now][k]=f[past][k-(1<<(j-1))-(1<<j)];
else
{
if (bit1) f[now][k]=f[past][k]+f[past][k-(1<<(j-1))+(1<<j)];
else f[now][k]=f[past][k]+f[past][k-(1<<j)+(1<<(j-1))];
}
}
if (j==m)
{
for(int k=(1<<(m+1));k>=0;k--)
{
if (k%2) f[now][k]=0;
else f[now][k]=f[now][k>>1];
}
}
swap(now,past);
}
printf("Case %d: There are %lld ways to eat the trees.
",t,f[past][0]);
}
return 0;
}