Constructing Chimney HDU - 4332 DP+矩阵快速幂

Constructing Chimney HDU - 4332
前置需求:DP,矩阵快速幂

Part 0

DP的核心思想就是“层”的定义。而本题有一个较为明显的方向,就是本题中的层。对于一个烟囱,我们可以一层一层的搭。对于每一层,我们知道了这层有那几个地方已经被竖着摆放的砖头放置后,就可枚举推出放满这层后的下一层的状态。而每层的状态都可以状态压缩。

Part 1

举个栗子

原始状态

(首先,这个烟囱是从下向上搭的,以下省略号表示放在本层的砖块,省略号方向表示砖块放置的方向,0表示本层这个位置没有砖块,U表示这个位置是一个横跨本层与上一层的砖块,D表示这个位置是一个横跨本层与下一层的砖块)
[ 0 0 0 0 0 0 0 0 0 ] \left[ \begin{matrix} 0 & 0 & 0\\ 0 & 0 & 0\\ 0 & 0 & 0\\ \end{matrix} \right] 000000000
随便进行一次操作后本层状态为。
[ ⋮ ⋯ ⋯ ⋮ 0 U U ⋯ ⋯ ] \left[ \begin{matrix} \vdots & \cdots & \cdots \\ \vdots & 0 & U \\ U & \cdots & \cdots \\ \end{matrix} \right] U0U
那么上一层的状态就是这样子。
[ 0 0 0 0 0 D D 0 0 ] \left[ \begin{matrix} 0 & 0 & 0 \\ 0 & 0 & D \\ D& 0 & 0\\ \end{matrix} \right] 00D0000D0
以上操作可以通过DFS完成,这样就实现了状态的转移(由于每层状态的每个位置都只有0和D这两种符号,故用二进制装压即可。最后的答案就是搭到第n+1层时第n+1层全为0的状态。然后可以因此处理出矩阵,再用矩阵快速幂。

Part 3

如果直接存2^9个状态,是会TLE的。

这里因为中间的那一个位置肯定没有砖块,故不用存。此时每层有28个状态,又因为每一层的状态肯定只有砖块的数量为奇数时才合法(这个还是蛮好理解的)。故可以吧这28个状态映射为2^7个状态。

若追求速度可以将转移矩阵处理之后直接赋值而不是在程序中处理。

AC代码及部分解释:

#include<cstdio>
#include<cstring>
#define P 1000000007
#define M 258
int ID[M];
struct Matrix{//矩阵快速幂板子 
	int n,m;
	int num[M][M];
	void resize(int x,int y){n=x,m=y;}
	void Init(){for(int i=0;i<n;i++)for(int j=0;j<m;j++)num[i][j]=i==j;}
	void clear(){memset(num,0,sizeof(num));}
	Matrix operator *(const Matrix &x)const{
		Matrix res;
		res.resize(n,x.m);
		for(int i=0;i<res.n;i++){
			for(int j=0;j<res.m;j++){
				res.num[i][j]=0;
				for(int k=0;k<m;k++)res.num[i][j]=(res.num[i][j]+1ll*num[i][k]*x.num[k][j]%P)%P;
			}
		}
		return res;
	}
	void Print(){
		for(int i=0;i<n;i++){
			for(int j=0;j<m;j++)printf("%d ",num[i][j]);
			puts("");
		}
	}
}A,B,S;
Matrix Mul(Matrix a,int b){
	Matrix res;
	res.resize(a.n,a.m);
	res.Init();
	while(b){
		if(b&1)res=res*a;
		a=a*a;
		b>>=1;
	}
	return res;
}
int Pc;//当前的初始状态 
int F(int x){return x-(x>=4);}
void dfs(int now,int step,int nxt){//now当时本层的状态,step当前操作哪块砖块,nxt当前上一层的状态 
	if(step==4){
		dfs(now,step+1,nxt);
		return;
	}
	if(step==9){
		B.num[ID[nxt]][ID[Pc]]++;//注意这个要和初始的状态算,而不是当前本层的状态 
		return;
	}
	if((1<<F(step))&now){//把step转换为他在二进制里的位置(也就是去掉中间那一个位置的砖块后它的位置) 
		dfs(now,step+1,nxt);
		return;
	}
	dfs(now|(1<<F(step)),step+1,nxt|(1<<F(step)));//这个位置竖着放 
	if(step!=3&&step%3!=2&&((now&(1<<F(step+1)))==0))dfs(now|(1<<F(step))|(1<<F(step+1)),step+1,nxt);//放-字形 
	if(step!=1&&step<=5&&((now&(1<<F(step+3)))==0))dfs(now|(1<<F(step))|(1<<F(step+3)),step+1,nxt);//放1字形 
}
int Count(int x){//计算这个二进制数有几位 
	int cnt=0;
	for(int i=x;i>0;i-=i&-i)cnt++;
	return cnt;
}
void Init(){
	int I=1<<8,id=-1;
	for(int i=0;i<I;i++){
		if(Count(i)%2==1)continue;
		ID[i]=++id;//把状态映射一下,减少状态数量 
	}
	A.resize(1,id+1);//id为当前的状态为0~id,故有id+1个状态 
	A.clear();
	A.num[0][0]=1;
	B.resize(id+1,id+1);
	B.clear();
	for(int i=0;i<I;i++){
		if(Count(i)%2==1)continue;
		Pc=i;
		dfs(i,0,0);
	}
}
int main(){
	Init();
	int T;
	scanf("%d",&T);
	for(int Case=1;Case<=T;Case++){
		int n;
		scanf("%d",&n);
		S=A*Mul(B,n);//向上搭n层 
		printf("Case %d: %d\n",Case,S.num[0][0]); 
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值