【题意】一个人在SE(向南向东)图上砍树,向南砍一棵树其南边的一格被占。被占的格子不能砍树,也不能再有树倒在这个格子上。一幅W*H的图有2^(W*H)种状态,问砍下的树之和为多少。
【思路】因为W(1~7)很小,可以状压。f[i][s]表示前i行其中第i行对第i+1行的影响为s的(1为有影响)砍下的树之和,g[i][s]表示前i行其中第i行对第i+1行的影响为s的方案数有多少个。状态转移为:(见代码)……
【代码】
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;
const int maxw=1<<8;
const int maxh=42;
int w,h,mod;
int f[maxh][maxw],g[maxh][maxw];
inline int get(){
char c;while(!isdigit(c=getchar()));
int v=c-48;while(isdigit(c=getchar()))v=v*10+c-48;
return v;
}
inline void add(int &a,int b){b%=mod;if((a+=b)>mod)a-=mod;}
int main(){
w=get();h=get();mod=get();
int ks=1<<w;
for(int d=0;d<ks;++d){//枚举砍树方向(1为向南) 从左到右 如:3为1100000
int s0=0,stp=0;bool flag=0;
for(int i=1;i<=w;++i){
if(flag){flag=0;continue;}
if(d&(1<<(i-1))){
++stp;
s0|=1<<(i-1);
}
else{
++stp;
if(i<w)flag=1;
else s0|=1<<(i-1);
}
}
add(f[1][s0],stp);//砍下的树
add(g[1][s0],1);//方案数
}
for(int i=2;i<h;++i){
for(int sl=0;sl<ks;++sl){
for(int d=0;d<ks;++d){
int s0=0,stp=0;bool flag=0;
for(int j=1;j<=w;++j){
if(flag){flag=0;continue;}//被本行的挡
if(sl&(1<<(j-1)))continue;//被上一行的挡
if(d&(1<<(j-1))){
++stp;
s0|=1<<(j-1);
}
else{
++stp;
if(j<w && (!(sl&(1<<j))))flag=1;
else s0|=1<<(j-1);
}
}
add(f[i][s0],f[i-1][sl]+(ll)stp*g[i-1][sl]);//状态转移
add(g[i][s0],g[i-1][sl]);//状态转移
}
}
}
for(int sl=0;sl<ks;++sl){
for(int d=0;d<ks;++d){
int stp=0;bool flag=0;
for(int i=1;i<=w;++i){
if(flag){flag=0;continue;}
if(sl&(1<<(i-1)))continue;
if(sl&(1<<i))continue;
if(i==w)continue;
++stp,flag=1;
}
add(f[h][0],f[h-1][sl]+(ll)stp*g[h-1][sl]);
}
}
printf("%d\n",f[h][0]);
return 0;
}