hbcpc problem J 骨牌游戏(状压dp+矩阵快速幂)

文章讲述了如何使用矩阵快速幂和状态压缩动态规划方法解决覆盖特定大小棋盘的问题。作者最初尝试了简单的状态压缩dp,但因数据范围过大导致超时,然后通过观察斐波那契数列的规律,结合矩阵快速幂优化算法,最终成功解决了当n为奇数时,覆盖n*m棋盘的方案数问题。
摘要由CSDN通过智能技术生成

题目要求:

用1*2的骨牌完全覆盖n*m棋盘,问有多少种方案

1<=n<=5,1<=m<=10^18

答案998244353取模

思路

你可以当我虚线下面是废话,我想了两天终于想出来了,我是菜鸡

总的来说就是矩阵快速幂加状态压缩dp

#include<bits/stdc++.h> 
#define int long long
using namespace std;
const int N=5,M=1<<N,mod=998244353;
int n,m;
bool st[M];
int A[M][M],tmp[M][M],res[M][M];
 void MXMP(int a[][M], int b[][M])
{
	memset(tmp, 0, sizeof tmp);//tmp用来存放相乘的结果

	for (int i = 0; i < (1<<n); i++) {
		for (int j = 0; j < (1<<n); j++) {
			for (int k = 0; k < (1<<n); k++) {
				tmp[i][j] = (tmp[i][j] + (a[i][k] * b[k][j]) % mod) % mod;//记得摸mod
			}
		}
	}

	for (int i = 0; i < (1<<n); i++) {
		for (int j = 0; j < (1<<n); j++) {
			a[i][j] = tmp[i][j];//记得把tmp赋值给a
		}
	}
}

void powermod(int A[][M], int x)
{
	memset(res, 0, sizeof res);//res存放结果
	for (int i = 0; i < (1<<n); i++) {
		for (int j = 0; j < (1<<n); j++) {
			res[i][j] = A[i][j];
		}
	}
	while (x) {
		if (x & 1) MXMP(res, A);//x为奇数就将A与res相乘
		MXMP(A, A);//A自身相乘
		x >>= 1;//x除以二
	}

	for (int i = 0; i < (1<<n); i++) {//将结果赋值到A
		for (int j = 0; j < (1<<n); j++) {
			A[i][j] = res[i][j];
		}
	}
}
signed main()
{
	ios_base::sync_with_stdio(0); cin.tie(0),cout.tie(0);
	cin>>n>>m;
	if((n&1)&&(m&1)){//判断是否都是奇数
		cout<<0;
		return 0;
	}
	memset(st,true,sizeof st);//记录是否可以放
	for(int i=0;i<(1<<n);i++){
		int cnt=0;
		for(int j=0;j<n;j++){
			if((i>>j)&1){
				if(cnt&1){
					st[i]=false;
					break;
				}
			}else cnt++;
		}
		if(cnt&1) st[i]=false;
	}
	memset(A,0,sizeof A);
	for(int j=0;j<(1<<n);j++){
		for(int k=0;k<(1<<n);k++){
			if((j&k)==0&&st[j|k]){
				A[j][k]=1; //记录是由哪个状态可以继承过来
			}
		}
	}
	m-=1;//因为res是由A赋值过去的所以res*A会多算一次得减一
	powermod(A,m);//矩阵快速幂
	cout<<A[0][0];
	/*for(int i=0;i<(1<<n);i++){
		for(int j=0;j<(1<<n);j++){
			cout<<A[i][j]<<' ';
		}
		cout<<'\n';
	}*/
	
}

---------------------------------------------------------------------------------------------------------------------------------

原本没看范围以为就是一道简单的状态压缩dp很开心的按版子打上去交了一发就超时了

后面一看范围10的18次方,比赛时也是没有做出来

这是状态压缩dp的代码(超时)

#include<bits/stdc++.h> 
#define int long long
using namespace std;
const int N=5,M=1<<N;
int n,m;
bool st[M];
int f[M][M];
signed main()
{
	ios_base::sync_with_stdio(0); cin.tie(0),cout.tie(0);
	cin>>n>>m;
	memset(st,true,sizeof st);
	for(int i=0;i<(1<<n);i++){
		int cnt=0;
		for(int j=0;j<n;j++){
			if((i>>j)&1){
				if(cnt&1){
					st[i]=false;
					break;
				}
			}else cnt++;
		}
		if(cnt&1) st[i]=false;
	}
	memset(f,0,sizeof f);
	f[0][0]=1;
	for(int i=1;i<=m;i++){
		for(int j=0;j<(1<<n);j++){
			for(int k=0;k<(1<<n);k++){
				if((j&k)==0&&st[j|k]){
					f[i][j]+=f[i-1][k];
				}
			}
		}
	}
	cout<<f[m][0];
}

后面我们还是找了一下规律当n=1 就是0,1,0,1,0,1

n=2就是1,2,3,5,8,13

发现是一个斐波那契数列,这时候m可以取到10^18正常求肯定会超时,但是斐波那契还可以用矩阵快速幂来求,这时候我就有大致思路,是不是整体都可以用矩阵快速幂来优化

矩阵快速幂

下面是求斐波那契的代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 998244353;
int A[2][2] = { {1,1},{1,0} };
int res[2][2], n;
int tmp[2][2];

void MXMP(int a[][2], int b[][2])
{
	memset(tmp, 0, sizeof tmp);

	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 2; j++) {
			for (int k = 0; k < 2; k++) {
				tmp[i][j] = (tmp[i][j]%mod + (a[i][k] * b[k][j]) % mod) % mod;
			}
		}
	}

	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 2; j++) {
			a[i][j] = tmp[i][j]%mod;
		}
	}
}

void powermod(int A[][2], int x)
{
	memset(res, 0, sizeof res);
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 2; j++) {
			res[i][j] = 1;
		}
	}
	while (x) {
		if (x & 1) MXMP(res, A);
		MXMP(A, A);
		x >>= 1;
	}

	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 2; j++) {
			A[i][j] = res[i][j];
		}
	}
}
signed main()
{
	ios_base::sync_with_stdio(0); cin.tie(0), cout.tie(0);
	cin >> n;
	n-=1;
	if(n==0||n==1){
		cout<<1;
		return 0;
	}
	powermod(A, n - 2);
	int q = (A[0][1] + A[0][0])%mod;
	cout << q;
}

然后我n=3是1,0,3,0,11,0

我想这先找一下偶数的规律

然后去找了n=4  1,1,5,11,36,95,281

 看他数出现的规律然后去推断是由哪个数继承过来的

我把出现过数字的号码记下来了

 看上图1可以加到12456进去,2可以加到12进去

后面想到可以用矩阵就是右边那图

可以写成n==4的代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 998244353;
int A[6][6] = { {1,1,0,1,1,1},{1,1,0,0,0,0},{0,0,0,1,0,0},{1,0,1,0,0,0},{1,0,0,0,1,0},{1,0,0,0,0,0}};
int res[6][6], n;
int tmp[6][6];

void MXMP(int a[][6], int b[][6])
{
	memset(tmp, 0, sizeof tmp);

	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			for (int k = 0; k < 6; k++) {
				tmp[i][j] = (tmp[i][j] + (a[i][k] * b[k][j]) % mod) % mod;
			}
		}
	}

	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			a[i][j] = tmp[i][j];
		}
	}
}

void powermod(int A[][6], int x)
{
	memset(res, 0, sizeof res);
	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			res[i][j] = A[i][j];
		}
	}
	while (x) {
		if (x & 1) MXMP(res, A);
		MXMP(A, A);
		x >>= 1;
	}

	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++) {
			A[i][j] = res[i][j];
		}
	}
}
signed main()
{
	ios_base::sync_with_stdio(0); cin.tie(0), cout.tie(0);
	cin >> n;
	n-=2;
	if(n==-1){
		cout<<1;
		return 0;
	} 
	powermod(A, n);
	int q = A[0][0];
	cout << q;
}

做到这里突然就悟了,就是打一张表看能不能继承在用一下快速幂就行了

卧槽我太聪明了

#include<bits/stdc++.h> 
#define int long long
using namespace std;
const int N=5,M=1<<N,mod=998244353;
int n,m;
bool st[M];
int A[M][M],tmp[M][M],res[M][M];
 void MXMP(int a[][M], int b[][M])
{
	memset(tmp, 0, sizeof tmp);//tmp用来存放相乘的结果

	for (int i = 0; i < (1<<n); i++) {
		for (int j = 0; j < (1<<n); j++) {
			for (int k = 0; k < (1<<n); k++) {
				tmp[i][j] = (tmp[i][j] + (a[i][k] * b[k][j]) % mod) % mod;//记得摸mod
			}
		}
	}

	for (int i = 0; i < (1<<n); i++) {
		for (int j = 0; j < (1<<n); j++) {
			a[i][j] = tmp[i][j];//记得把tmp赋值给a
		}
	}
}

void powermod(int A[][M], int x)
{
	memset(res, 0, sizeof res);//res存放结果
	for (int i = 0; i < (1<<n); i++) {
		for (int j = 0; j < (1<<n); j++) {
			res[i][j] = A[i][j];
		}
	}
	while (x) {
		if (x & 1) MXMP(res, A);//x为奇数就将A与res相乘
		MXMP(A, A);//A自身相乘
		x >>= 1;//x除以二
	}

	for (int i = 0; i < (1<<n); i++) {//将结果赋值到A
		for (int j = 0; j < (1<<n); j++) {
			A[i][j] = res[i][j];
		}
	}
}
signed main()
{
	ios_base::sync_with_stdio(0); cin.tie(0),cout.tie(0);
	cin>>n>>m;
	if((n&1)&&(m&1)){//判断是否都是奇数
		cout<<0;
		return 0;
	}
	memset(st,true,sizeof st);//记录是否可以放的了
	for(int i=0;i<(1<<n);i++){
		int cnt=0;
		for(int j=0;j<n;j++){
			if((i>>j)&1){
				if(cnt&1){
					st[i]=false;
					break;
				}
			}else cnt++;
		}
		if(cnt&1) st[i]=false;
	}
	memset(A,0,sizeof A);
	for(int j=0;j<(1<<n);j++){
		for(int k=0;k<(1<<n);k++){
			if((j&k)==0&&st[j|k]){
				A[j][k]=1; //记录是由哪个状态可以继承过来
			}
		}
	}
	m-=1;//因为res是由A赋值过去的所以res*A会多算一次得减一
	powermod(A,m);//矩阵快速幂
	cout<<A[0][0];
	/*for(int i=0;i<(1<<n);i++){
		for(int j=0;j<(1<<n);j++){
			cout<<A[i][j]<<' ';
		}
		cout<<'\n';
	}*/
	
}

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值