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倒过来
- 叠起来
n大于2的情况:
为了避免重复,不考虑拼接中有n=2的情况;
n=4,6时,有上下颠倒的上图两种方式,依次类推,n=2k(k>=2)时,都有两种方式。
现在考虑n为一般偶数情况下的值:
假设最右边为2列,则左边有f(n-2)种方式,共有3f(n-2)种;
假设最右边为4列,则左边有f(n-4)种方式,共有2f(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)=3f(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;
}
}
}
本题数据较大,要求得出取模过后的结果,要解决几个问题:
- 数据相乘几次后,可能会变得非常非常大,可能超出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)
这样就不会超过最大的数据了。 - 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(n−2)]=[4f(n−2)−f(n−4)f(n−2)]=[41−10]∗[f(n−2)f(n−4)]
[ 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(n−2)f(n−4)]=[4f(n−4)−f(n−6)f(n−4)]=[41−10]∗[f(n−4)f(n−6)]
…
[
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(n−4)]=[4f(n−4)−f(n−6)f(n−4)]=[41−10]2∗[f(n−4)f(n−6)]
= [ 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] =[41−10]2n−1∗[f(2)f(0)]=[41−10]2n−1∗[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);
}
}
}