题目链接:
https://hihocoder.com/problemset/problem/1151
题目:
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
上一周我们研究了2xN的骨牌问题,这一周我们不妨加大一下难度,研究一下3xN的骨牌问题?
所以我们的题目是:对于3xN的棋盘,使用1x2的骨牌去覆盖一共有多少种不同的覆盖方法呢?
首先我们可以肯定,奇数长度一定是没有办法覆盖的;对于偶数长度,比如2,4,我们有下面几种覆盖方式:
输入
第1行:1个整数N。表示棋盘长度。1≤N≤100,000,000
输出
第1行:1个整数,表示覆盖方案数 MOD 12357
样例输入
62247088
样例输出
4037
题目大意:
嗯~中文题。。略过这一步0.0
题目分析:
重头戏来啦0.0 这道题其实是个系列题,从最简单的2*n的面积,到这道题的3*n的面积,后面还有k*n的面积的升级版。挺有意思的题目0.0
那么,我们应该怎么去解决这个问题呢?有两种方法这里会逐一去分析,第一种是矩阵快速幂,第二种代码简单但难想,速度也慢,就是递推公式的方法了(其实原题自带的题解就解释了矩阵快速幂的方法啦)(矩阵快速幂是正解0.0)
一、矩阵快速幂(图片来自题目自带的题解)
假设我们已经放好了一些骨牌,对于当前最后一列(第i列)骨牌,可能有8种情况,对于上面的这8种状态,我们用数字来标记它们。以放置骨牌的格子为1,未放置为0,转化为2进制数,进行状态压缩,可以得到以下状态:
注意,在讨论第i行的状态的时候,第i-1行一定是装满了的。
这种情况看似是从状态1变成了状态0,其实是不对的。它不满足我们约定的放置方法,本质是第i行的状态1变成了第i行的状态7,而实际上我们应该放置的是第i+1行。
在这种规则之下,我们就可以判断从 i 列的 某幢状态 能否转移为 第 i+1 列的状态
通过枚举8种状态到8种状态的转移,我们可以得到一个8x8的矩阵M(空白的地方均为0):
m[i][j]表示从状态i变成状态j的方案数。
然后我们就可以用A * M^n所求出的结果表示第n行各种状态的方案数。(其中A应该初始化为{0, 0, 0, 0, 0, 0, 0, 1}),而对于我们寻求的答案,自然也是第n行放置为状态7的方案数了。然后就是简单粗暴的矩阵快速幂。
二、递推公式法(来自聪明的CGX同学)
首先,我们先写出如下几种情况:
在3*2的面积中所有可能的摆放方式:
那么,在3*n (n为偶数并且 n > 2)的面积中,除去上面3种方式的组合,摆放方式如下:
或者把最上面那一行移到上面去(这儿省略不画)
如果除去第一种情况的互相组合,我们只可以这样摆放。
那么,根据这两种情况,我们可以得到一个递推式:
F(n)= 3 * F(n-2)+ 2 * F(n-4)+ 2 * F(n-6)+ ... +2 * F(2)
代换变量可以得到
F(n-2)= 3 * F(n-4)+ 2 * F(n-6)+ 2 * F(n-8)+ ... + 2 * F(2)
则对一式变形可得:
F(n)= 4 * F(n-2)- F(n-2)+ 2 * F(n-4)+ 2 * F(n-6)+ ... +2 * F(2)
带入二式得:
F(n)= 4 * F(n-2)- (3 * F(n-4)+ 2 * F(n-6)+ 2 * F(n-8)+ ... + 2 * F(2))+ 2 * F(n-4)+ 2 * F(n-6)+ ... +2 * F(2)
即:
F(n)= 4 * F(n-2)- F(n-4)
这样这道题就解决啦0.0
题目给的10s所以不会超时 当然啦,用矩阵快速幂优化这个递推式,就会超级快啦0.0
代码:(愉快的代码时间)
矩阵快速幂:
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long ll;
const int MAXN=8;
const ll MOD=12357;
struct Matrix{
ll a[MAXN][MAXN];
void init(){
memset(a,0,sizeof(a));
for(int i=0;i<MAXN;i++)
a[i][i]=1;
}
};
Matrix mul(Matrix a, Matrix b)
{
Matrix ans;
for(int i=0;i<MAXN;i++)
{
for(int j=0;j<MAXN;j++)
{
ans.a[i][j]=0;
for(int k=0;k<MAXN;k++)
ans.a[i][j]=(ans.a[i][j]+(a.a[i][k]*b.a[k][j])%MOD)%MOD;
}
}
return ans;
}
Matrix qpow(Matrix a, ll n)
{
Matrix ans;
ans.init();
while(n)
{
if(n&1)
ans = mul(ans, a);
a=mul(a,a);
n>>=1;
}
return ans;
}
int main()
{
ll n;
while(scanf("%lld",&n)!=EOF)
{
Matrix a;
memset(a.a,0,sizeof(a.a));
a.a[0][7]=1;a.a[1][6]=1;a.a[2][5]=1;a.a[3][4]=1;a.a[4][3]=1;a.a[5][2]=1;a.a[6][1]=1;a.a[7][0]=1;
a.a[3][7]=1;a.a[6][7]=1;a.a[7][3]=1;a.a[7][6]=1;
Matrix ans=qpow(a,n);
printf("%lld\n",ans.a[7][7]);
}
return 0;
}
递推:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN=(int)1e8+10;
const int MOD=12357;
int main()
{
int n;
scanf("%d",&n);
if(n&1)
printf("0\n");
else if(n==0)
printf("1\n");
else if(n==2)
printf("3\n");
else
{
n/=2;
long long a=1,b=3,t;
for(int i=2;i<=n;i++)
{
t=((4*b-a)%MOD+MOD)%MOD;
a=b;b=t;
}
printf("%lld\n",t);
}
return 0;
}