矩阵快速幂优化状压DP--骨牌

题目
有一个 H × W H\times W H×W 的棋盘。 她想知道,用 1 × 2 1\times 2 1×2 的骨牌覆盖这个棋盘,有多少种不同的方案。 一个合法的方案满足所有骨牌的边都与棋盘的边平行,骨牌之间没有重叠,并且骨牌的所有部分都在棋盘内。 两种方案不同当且仅当在一种方案中,一个格子被骨牌覆盖,在另一种方案中,这个格子没有被骨牌覆盖。
H ≤ 5 , W ≤ 1 0 18 H\le 5,W\le 10^{18} H5,W1018

solution:
(这是一道巨巨巨难的题,虽然代码不长但是非常难想

第一眼看到这道题的时候想到了以前的一道类似的题,也是矩阵优化 d p dp dp,但不同的是那道必须填满,而这道不需要填满而且两种方案不同仅当有格子的覆盖情况不同,这样的话,就会出现多种摆放骨牌方法对应同一种覆盖情况,就不能用普通的状压做了

首先,对这种多种对应同一种的情况,考虑状态再次压缩,设 f [ i ] [ S ] f[i][S] f[i][S]表示前 i i i列, S S S表示一个凸出的插头形状的集合,这个集合中的所有插头形状都可以对应相同的一种覆盖方式,比如说 10110 10110 10110 10000 10000 10000就可以放到一个集合里

这样设状态看起来是 2 2 H 2^{2^H} 22H的,只能通过 H ≤ 3 H\le 3 H3的点,但通过搜索可以发现合法状态很少,最多只有 90 90 90多种,对于这种看起来很多其实很少的状态,可以先搜索预处理出所有合法的集合记到一个数组 g [ i ] g[i] g[i]里面,然后通过排序二分等操作就可以预处理出矩阵,然后的步骤就很简单了

要特别说一下这里的 d f s dfs dfs过程,比较神奇,首先 d f s ( i n t   x ) dfs(int\ x) dfs(int x)表示当前已经搜到了 x x x集合,要继续搜和 x x x覆盖情况不一样的,这里采用的方法是, 2 H 2^H 2H循环一次所有的插头形状假设为 i i i,然后看 i i i x x x集合内的插头的关系, i f ( ( i & j ) = = j ) if((i\&j)==j) if((i&j)==j),就说明 i   x o r   j i\ xor\ j i xor j是一种不同的情况,然后就把可和 i   x o r   j i\ xor\ j i xor j覆盖情况相同的都处理出来,这里的方法是写一个递归函数,看有没有连续的两个 1 1 1,有的话说明可以用竖块替代,也是一个合法的。

具体看代码吧,挺难理解的 q w q qwq qwq

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn 100
#define int long long
using namespace std;
int n,m,mod,ed,g[maxn],tot,ans;

inline int rd(){
	int x=0,f=1;char c=' ';
	while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	return x*f;
}

struct Mat{
	int a[maxn][maxn];
	Mat(){memset(a,0,sizeof a);}
	Mat operator *(const Mat &x) const{
		Mat ret;
		for(int k=0;k<tot;k++)
			for(int i=0;i<tot;i++)
				for(int j=0;j<tot;j++)
				(ret.a[i][j]+=a[i][k]*x.a[k][j]%mod)%=mod;
		return ret;
	}
}f,s;

inline Mat qpow(Mat x,int k){
	Mat ret;
	ret.a[0][0]=1;
	while(k){
		if(k&1) ret=ret*x;
		x=x*x; k>>=1;
	} return ret;
}

inline void change(int &k,int x){
	k|=(1<<x);
	for(int i=0;i<n-1;i++)
		if((x>>i&3)==3) change(k,x^(3<<i));
}

inline void dfs(int x){
	int i,j,y,l,r,mid;
	l=0,r=tot;
	while(l<r){
		mid=(l+r)>>1;
		if(g[mid]<x) l=mid+1;
		else r=mid-1;
	}r++;
	if(g[r]==x) return;
	g[tot++]=x; sort(g,g+tot);
	for(i=0;i<ed;i++){
		for(y=j=0;j<ed;j++)
			if((x>>j&1) && (i&j)==j)
				change(y,i^j);
		if(y) dfs(y);
	}
}

signed main(){
	n=rd(); m=rd(); mod=rd(); ed=1<<n;
	dfs(1);
	for(int cur=0;cur<tot;cur++){
		int x=g[cur];
		for(int i=0;i<ed;i++){
			int y=0;
			for(int j=0;j<ed;j++)
				if((x>>j&1) && (i|j)==i) change(y,i^j);
			if(y){
				int l=0,r=tot,mid;
				while(l<r){
					mid=(l+r)>>1;
					if(g[mid]<y) l=mid+1;
					else r=mid-1;
				}r++;
				f.a[cur][r]++;
			}
		}
	}
	s=qpow(f,m);
	for(int i=0;i<tot;i++)
		if(g[i]&1) (ans+=s.a[0][i])%=mod;
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值