ssoj2467树(状压dp)

【题意】一个人在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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值