易错点:
需要熟悉状压DP的基本操作.
按位或保证了物各有主.
按位与在获取了两行同样是横着方块(1)的部分后,等价于保证了横着方块的偶数性质(实际上只是排除了上下分别是01的方块的影响).
结论1:在合法状态下,对于每个横块的正上方两个格子中任意一个格子,一定属于某个横块的一部分或某个竖块的下半部.
证明:
假设有一个横块的正上方两格中的某格为一个竖块的上半部,由定义可知该情况不成立.
对于任意一个格子,只有可能为以下三种状态之一:竖块上半部、竖块下半部、半个横块.
由(1)(2)可知,一个横块的上方两格中的某格的一部分只有可能为为竖块下半部或半个横块.
结论1得证.
结论2:如果状态2可作为状态1的下一行,那么假设状态1和状态2中两状态都为横格的部分提取出来,并将提取出来的部分设为1,其他部分设为0,那么新状态的连续1的个数一定为偶数.
证明:由结论1可知结论2成立.
结论3:对于每个竖块,假设上竖块为0,下竖块为1,那么如果状态2可作为状态1的下一行,每个竖块的位置的或运算结果一定为1.
证明:由定义可知结论3成立.
结论4:假设横块为1,上竖块为0,下竖块为1,如果状态2可作为状态1的下一行,那么状态1与状态2的或运算结果一定全为1.
证明:由结论3可知结论4成立.
结论4可保证竖块的合法性,但不能保证横块的合法性;结论2可保证横块的合法性,但不能保证竖块的合法性。因此,结合结论2与结论4即可保证两相邻状态的合法性.
("久旱"和"甘霖"同时满足是"久旱逢甘霖"的充要条件,"萌"和"眼镜"同时满足是"栗山未来"的充要条件,"作画崩坏"和"大IP"同时满足是"JC社"的充要条件)
#include
#include
#include
using namespace std;
long long dp[13][1<<13];
bool check(int s){
int rest=0;
while(s){
if(s&1)rest+=1;
else if(rest&1)return 0;
s>>=1;
}
if(rest&1)return 0;
return 1;
}
int main(){
int n,m;
while(~scanf("%d%d",&n,&m)){
if(n==0||m==0)return 0;
memset(dp,0,sizeof(dp));
int tot=(1<
for(int i=0;i
if(check(i))dp[1][i]=1;
}
for(int i=1;i
for(int j=0;j
if(!dp[i][j])continue;
for(int k=0;k
if((k|j)!=tot-1||(!check(k&j)))continue;
dp[i+1][k]+=dp[i][j];
}
}
}
printf("%lld\n",dp[n][tot-1]);
}
}
//该份代码与上份代码有较多不同.
#include
#include
#include
#include
#include
#include
using namespace std;
int n, m;
long long f[12][1 << 11];
bool in_s[1 << 11];
int main() {
while (cin >> n >> m && n) {
for (int i=0;i
bool cnt=0,isNotLicit=0;//cnt:当前有几个连续的横块(0:偶数,1:奇数) isNotLicit:是否不合法
for (int j = 0; j < m; j++)//遍历每一位
if ((i>>j)&1){//处理竖块
if(cnt){//横块与竖块下部重叠,即状态非法
isNotLicit=1;
break;
}
}else cnt ^= 1;//处理横块(利用了0/1变换的原理)
in_s[i] = (isNotLicit || cnt) ? 0 : 1;//0:不合法,1:合法
}
f[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 0; j < 1 << m; j++) {
f[i][j] = 0;
for (int k = 0; k < 1 << m; k++)
if ((j&k) == 0 && in_s[j | k])
f[i][j] += f[i - 1][k];
}
cout << f[n][0] << endl;
}
}