Til Triling(modified)

Til Triling(modified)(详解)动态规划、矩阵快速幂

Description

In how many ways can you tile a 3xn rectangle with 2x1 dominoes? Find the answer taken modulo 9973.

Input

Input consists of several test cases followed by a line containing -1. Each test case is a line containing an integer 0 <= n <= 109.

Output

For each test case, output one non negative integer, which is the number of possible tilings modulo 9973.

Sample Input

2
8
12
-1

Sample Output

3
153
2131

Source

Waterloo local 2005.09.24

题解(同时对比未修改数据的情况)

题目的大意是:用2x1大小的方格填满3xn的方格,对于给定的n值,有多少种不同的拼接方法。

例子

经过思考,很明显n不可能为奇数,设填满n列有f(n)种方式,有以下几种可能的拼接方式

n=2时:

  1. 这样在这里插入图片描述
  2. 1倒过来
  3. 叠起来在这里插入图片描述

n大于2的情况:
为了避免重复,不考虑拼接中有n=2的情况;

在这里插入图片描述
n=4,6时,有上下颠倒的上图两种方式,依次类推,n=2k(k>=2)时,都有两种方式。

现在考虑n为一般偶数情况下的值:
假设最右边为2列,则左边有f(n-2)种方式,共有3f(n-2)种;
假设最右边为4列,则左边有f(n-4)种方式,共有2
f(n-4)种;
假设最右边为6列,则左边有f(n-6)种方式,共有2*f(n-4)种;

最右边为n列,左边有0列,共有2种。

得到递推式
f(n)=3f(n-2)+2f(n-4)+2f(n-6)+…+2f(n-(n-2))+2;
令f(0)=1,则有:
f(n)=3f(n-2)+2f(n-4)+2f(n-6)+…+2f(n-(n-2))+2f(0);
f(n-2)=3
f(n-4)+2f(n-6)+2f(n-8)+…+2f(n-(n-2))+2f(0);

两式相减可得:
f(n)=4*f(n-2)-f(n-4);

这样就得到了递推式。

对于常规的数据,直接递推求解。


#include <iostream>
#include <cstring>
using namespace std;
int main(){
 int dp[31], n;
 memset(dp, 0, sizeof(dp));
 dp[0]=1;
 dp[2]=3;
 while(true){
  cin>>n;
  if(n==-1) break;
  if(n%2==1)
   cout<<0<<endl;
  else if(dp[n]!=0)
   cout<<dp[n]<<endl;
  else{
   for(int i=4; i<=n; i++){
    dp[i] = 4*dp[i-2] - dp[i-4];
   }
   cout<<dp[n]<<endl;
  }
 }
}

本题数据较大,要求得出取模过后的结果,要解决几个问题:

  1. 数据相乘几次后,可能会变得非常非常大,可能超出long long int的数据量,所以不能先求结果再取模。可以运用取模运算的法则
    (a + b) % p = (a % p + b % p) % p (1)
    (a - b) % p = (a % p - b % p ) % p (2)
    (a * b) % p = (a % p * b % p) % p (3)
    这样就不会超过最大的数据了。
  2. n次相乘虽然时间复杂度为O(n),但当n非常非常大时,会非常耗时,所以可以运用快速幂算法,时间复杂度变成O(logn).针对这一题的递推式,运用矩阵快速幂
    分析如下:
    递推式为 f(n)=4*f(n-2)-f(n-4);

矩阵乘法

[ f ( n ) f ( n − 2 ) ] = [ 4 f ( n − 2 ) − f ( n − 4 ) f ( n − 2 ) ] = [ 4 − 1 1 0 ] ∗ [ f ( n − 2 ) f ( n − 4 ) ] \left[ \begin{matrix} f(n) \\ f(n-2) \end{matrix} \right] = \left[ \begin{matrix} 4f(n-2)-f(n-4)\\ f(n-2) \end{matrix} \right]=\left[\begin{matrix}4 & -1\\1& 0\end{matrix}\right]*\left[\begin{matrix}f(n-2)\\f(n-4)\end{matrix}\right] [f(n)f(n2)]=[4f(n2)f(n4)f(n2)]=[4110][f(n2)f(n4)]

[ f ( n − 2 ) f ( n − 4 ) ] = [ 4 f ( n − 4 ) − f ( n − 6 ) f ( n − 4 ) ] = [ 4 − 1 1 0 ] ∗ [ f ( n − 4 ) f ( n − 6 ) ] \left[ \begin{matrix} f(n-2) \\ f(n-4) \end{matrix} \right] = \left[ \begin{matrix} 4f(n-4)-f(n-6)\\ f(n-4) \end{matrix} \right]=\left[\begin{matrix}4 & -1\\1& 0\end{matrix}\right]*\left[\begin{matrix}f(n-4)\\f(n-6)\end{matrix}\right] [f(n2)f(n4)]=[4f(n4)f(n6)f(n4)]=[4110][f(n4)f(n6)]


[ f ( n ) f ( n − 4 ) ] = [ 4 f ( n − 4 ) − f ( n − 6 ) f ( n − 4 ) ] = [ 4 − 1 1 0 ] 2 ∗ [ f ( n − 4 ) f ( n − 6 ) ] \left[ \begin{matrix} f(n) \\ f(n-4) \end{matrix} \right] =\left[ \begin{matrix} 4f(n-4)-f(n-6)\\ f(n-4) \end{matrix} \right]=\left[\begin{matrix}4 & -1\\1& 0\end{matrix}\right]^2*\left[\begin{matrix}f(n-4)\\f(n-6)\end{matrix}\right] [f(n)f(n4)]=[4f(n4)f(n6)f(n4)]=[4110]2[f(n4)f(n6)]

= [ 4 − 1 1 0 ] n 2 − 1 ∗ [ f ( 2 ) f ( 0 ) ] = [ 4 − 1 1 0 ] n 2 − 1 ∗ [ 3 1 ] =\left[\begin{matrix}4 & -1\\1& 0\end{matrix}\right]^ {\frac{n}{2}-1}*\left[\begin{matrix}f(2)\\f(0)\end{matrix}\right]=\left[\begin{matrix}4 & -1\\1& 0\end{matrix}\right]^ {\frac{n}{2}-1}*\left[\begin{matrix}3\\1\end{matrix}\right] =[4110]2n1[f(2)f(0)]=[4110]2n1[31]

重点在于矩阵的构造,以及最后相乘次数的计算是易错点。
这样就转化为矩阵乘法了,再使用矩阵快速幂算法。
矩阵快速幂只需将快速幂里的普通乘法改为矩阵乘法即可。简单介绍一下快速幂算法,即将n次幂拆成两个n/2次幂相乘,再拆n/2次幂,直到不可拆为止,直接相乘得到结果。如果指数为奇数,保存此时底数,乘到结果中,指数减一,得到偶数的指数。

代码

#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
const int mod = 9973;
const int N = 2;
int tmp[N][N], res[N][N];
//@param a,b:相乘矩阵
//@param n为矩阵阶数
void MultiplyMatrix(int (*a)[N], int (*b)[N], int n) {
	//初始化为0
	memset(tmp, 0, sizeof(tmp));
	//矩阵乘法
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			for (int k = 0; k < n; k++)
				tmp[i][j] += ((a[i][k]%mod) * (b[k][j]%mod))%mod;
	//保存结果到a数组
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			a[i][j] = tmp[i][j];

 }

void PowMatrix(int (*a)[N],long long int n) {
	//初始化
	memset(res, 0, sizeof(res));
	for (int i = 0; i < N; i++)
		res[i][i] = 1;

	while (n) {
		//若为奇数
	   
		if (n & 1) {
			MultiplyMatrix(res, a, N);//res=res*a;
		}
		MultiplyMatrix(a, a, N);//a=a*a;
		n >>= 1;
	}
}


int main() {
	
	long long int n;
	while (scanf("%lld", &n)) {
		int ans = 0;
		if (n == -1) break;
		else if (n % 2) printf("0\n");
		else if (n == 0)printf("1\n");
		else if (n == 2)printf("3\n");
		else {
			int a[2][2] = { 4,-1,1,0 };
			n = n / 2 - 1;
			PowMatrix(a, n);

			ans = ((3 * res[0][0]) + res[0][1]) % mod;
			if (ans < 0) ans += mod;

			printf("%d\n", ans);
		}
	}

	

}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值