插头DP HDU – 1693
Eat the Trees 一个方格图,有一些点是障碍,求有用一些回路精确覆盖非障碍点的方案数。n,m≤11
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,T,w[20][20],now,past; //地图NXM,样例数,地图,两个点(就是下面f的一维下标)
ll f[2][5010]; //f[][s]表示到当前行S状态为止的方案数
int main(){
scanf("%d",&T); //输出样例数
for(int t=1;t<=T;t++){ //每个样例
memset(f,0,sizeof(f)); //清0
scanf("%d%d",&n,&m); //输出地图大小
for(int i=1;i<=n;i++) //行
for(int j=1;j<=m;j++) //列
scanf("%d",&w[i][j]); //输入
now=1,past=0; //这里的1与0没有特别含义,仅仅是用两个变量来表示两个序列,每次交换表示,注意是相邻两格交换
f[past][0]=1; //一开始轮廓线是0,方案数是1,因为全部格(其实没有格)都用线连完了
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);//读出当前格的右插头BIT1与下插头BIT2
//由这里可知插头的状态更新是由左往右是由低往高位
if (!bit1&&!bit2){ //两个插头都是0
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;} //插头不全是0但本格不可走方案直接0
else if (bit1&&bit2) f[now][k]=f[past][k-(1<<(j-1))-(1<<j)]; //两个插头都是1则转移两个插头都不要
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; //扫完一行,第M个插头是的最右边那格的右边,如果是1这状态就不存在,方案数清0
else f[now][k]=f[now][k>>1];//要传递到下一行上一轮的状态左移一位就是这一次的状态了
//扫到行末时打最竖的那个状态是在最右边,也就是最高位,所以要左移当前状态,就把最高位的打竖移到最低位,也就是下一行的最左端,同时M个上下插头不影响
}
}
swap(now,past);//进入下一格,注意,是下一格
}
printf("Case %d: There are %lld ways to eat the trees.\n",t,f[past][0]);//输出答案
}
return 0;
}
/*
Sample Input
2
6 3
1 1 1
1 0 1
1 1 1
1 1 1
1 0 1
1 1 1
2 4
1 1 1 1
1 1 1 1
Sample Output
Case 1: There are 3 ways to eat the trees.
Case 2: There are 2 ways to eat the trees.
*/