hihoCoder 1143 : 骨牌覆蓋問題·一(遞推,矩陣快速冪)
阿新 • • 發佈:2019-02-20
時間限制:10000ms
單點時限:1000ms
記憶體限制:256MB
那麼通過對上面的觀察,我們可以發現:
在任何一個放置方案最後,一定滿足前面兩種情況。而灰色的部分又正好對應了長度為N-1和N-2時的放置方案。由此,我們可以得到遞推公式:
f[n] = f[n-1] + f[n-2];
這個公式是不是看上去很眼熟?沒錯,這正是我們的費波拉契數列。
f[0]=1,f[1]=1,f[2]=2,...
1. 先計算出所有的{a^1, a^2, a^4 ... a^(2^j)},因為該數列滿足遞推公式,時間複雜度為O(logN)
2. 將指數n二進位制化,再利用公式將對應的a^j相乘計算出a^n,時間複雜度仍然為O(logN)
則總的時間複雜度為O(logN)
這種演算法因為能夠在很短時間內求出冪,我們稱之為“快速冪”演算法。
描述
骨牌,一種古老的玩具。今天我們要研究的是骨牌的覆蓋問題:
我們有一個2xN的長條形棋盤,然後用1x2的骨牌去覆蓋整個棋盤。對於這個棋盤,一共有多少種不同的覆蓋方法呢?
舉個例子,對於長度為1到3的棋盤,我們有下面幾種覆蓋方式:
輸入
第1行:1個整數N。表示棋盤長度。1≤N≤100,000,000
輸出
第1行:1個整數,表示覆蓋方案數 MOD 19999997
樣例輸入62247088
樣例輸出
17748018
【思路】矩陣快速冪
我們考慮在已經放置了部分骨牌(灰色)的情況下,下一步可以如何放置新的骨牌(藍色):
那麼通過對上面的觀察,我們可以發現:
在任何一個放置方案最後,一定滿足前面兩種情況。而灰色的部分又正好對應了長度為N-1和N-2時的放置方案。由此,我們可以得到遞推公式:
f[n] = f[n-1] + f[n-2];
這個公式是不是看上去很眼熟?沒錯,這正是我們的費波拉契數列。
f[0]=1,f[1]=1,f[2]=2,...
當N很小的時候,我們直接通過遞推公式便可以計算。當N很大的時候,只要我們的電腦足夠好,我們仍然可以直接通過遞推公式來計算。
但是我們學演算法的,總是這樣直接列舉不是顯得很Low麼,所以我們要用一個好的演算法來加速(裝X)。
事實上,對於這種線性遞推式,我們可以用 矩陣乘法來求第n項。對於本題Fibonacci數列,我們希望找到一個2x2的矩陣M,使得(a, b) x M = (b, a+b),其中(a,
b)和(b, a+b)都是1x2的矩陣。
顯然,只需要取M = [0, 1; 1, 1]就可以了:
進一步得到:
那麼接下來的問題是,能不能快速的計算出M^n?我們先來分析一下冪運算。由於乘法是滿足結合律的,所以我們有:
不妨將k[1]..k[j]劃分的更好一點?
其中(k[1],k[2]...k[j])2表示將n表示成二進位制數後每一位的數字。上面這個公式同時滿足這樣一個性質:
結合這兩者我們可以得到一個演算法:
1. 先計算出所有的{a^1, a^2, a^4 ... a^(2^j)},因為該數列滿足遞推公式,時間複雜度為O(logN)
2. 將指數n二進位制化,再利用公式將對應的a^j相乘計算出a^n,時間複雜度仍然為O(logN)
則總的時間複雜度為O(logN)
這種演算法因為能夠在很短時間內求出冪,我們稱之為“快速冪”演算法。
程式碼:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MOD=19999997;
LL N;
int i,j;
struct Matrlc
{
LL mapp[2][2];
} ans,base;
Matrlc unit= {1,0,0,1};
Matrlc mult(Matrlc a,Matrlc b) //矩陣乘法
{
Matrlc c;
for(int i=0; i<2; i++)
for(int j=0; j<2; j++)
{
c.mapp[i][j]=0;
for(int k=0; k<2; k++)
c.mapp[i][j]+=(a.mapp[i][k]*b.mapp[k][j])%MOD;
c.mapp[i][j]%=MOD;
}
return c;
}
LL pow(LL n) //快速冪運算
{
base.mapp[0][0] =base.mapp[0][1]=base.mapp[1][0]=1;
base.mapp[1][1]=0;
ans.mapp[0][0] = ans.mapp[1][1] = 1;// ans 初始化為單位矩陣
ans.mapp[0][1] = ans.mapp[1][0] = 0;
while(n)
{
if(n&1) ans=mult(ans,base);
base=mult(base,base);
n>>=1;
}
return ans.mapp[0][1]%MOD;
}
int main()
{
scanf("%lld",&N);
printf("%lld\n",pow(N+1)%MOD);
return 0;
}
/*
題目:
首先,這道題目是一道斐波那契數列的題目。
我們來分析一下,第三個圖形是如何由前兩個圖形組成。
______ _______
| | | 或 | |____|
|____|_| |__|____|
擴充套件到第n個圖形,我們有:
_____________ ______________
| | | 或 | |____|
|___________|_| |_________|____|
所以,f(n)=f(n-1)+f(n-2)
由於n可能會很大,所以我們需要一些計算的技巧。
斐波那契數列是可以由矩陣計算得到,如下:
[a,b]* [0,1] = [b,a+b]
[1,1]
令mat =[0,1]
[1,1]
那麼,理論上,我們乘以n個矩陣mat,就可以求得f(n),
但是n個矩陣相乘,時間複雜度為O(n),
這時候,我們採用快速冪運算來求解,可以把時間複雜度降為O(logn)。
*/
#include<string>
#include <iomanip>
#include<fstream>
#include<set>
#include<queue>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
//#include <sstream>
//#include "func.h"
//#include <list>
#include<stdio.h>
#include<iostream>
#include<string>
#include<memory.h>
#include<limits.h>
//#include<stack>
#include<vector>
#include <algorithm>
using namespace std;
#define MOD 19999997
class matrix22
{
public:
long long a1, a2;
long long b1, b2;
matrix22() :a1(0), a2(1), b1(1), b2(1){};
matrix22 operator*(const matrix22 tmp) // 過載矩陣乘法
{
matrix22 mat;
mat.a1 = (a1%MOD)*(tmp.a1%MOD) + (a2%MOD)*(tmp.b1%MOD);
mat.a2 = (a1%MOD)*(tmp.a2%MOD) + (a2%MOD)*(tmp.b2%MOD);
mat.b1 = (b1%MOD)*(tmp.a1%MOD) + (b2%MOD)*(tmp.b1%MOD);
mat.b2 = (b1%MOD)*(tmp.a2%MOD) + (b2%MOD)*(tmp.b2%MOD);
return mat;
}
};
/*
函式名 :main
函式功能:主函式
*/
int main(void)
{
int n;
scanf("%d", &n);
int dp1 = 1;
int dp2 = 2;
if (n <= 0) printf("0\n");
else if (n == 1) printf("1\n");
else if (n == 2) printf("2\n");
else
{
n -= 3;
matrix22 mat;
matrix22 ans;
while (n != 0)
{
//如果二進位制該位為1,則ans*mat
if (n & 1)
ans = ans*mat;
//mat每次與自身相乘,求得矩陣的1,2,4,8,16次方
mat = mat*mat;
n = (n >> 1);
}
//輸出f(n)
long long answer =( ans.a2 + 2 * ans.b2)%MOD;
cout << answer << endl;
}
return 0;
}