1. 程式人生 > >hihoCoder 1143 : 骨牌覆蓋問題·一(遞推,矩陣快速冪)

hihoCoder 1143 : 骨牌覆蓋問題·一(遞推,矩陣快速冪)

時間限制:10000ms 單點時限:1000ms 記憶體限制:256MB

描述

骨牌,一種古老的玩具。今天我們要研究的是骨牌的覆蓋問題:
我們有一個2xN的長條形棋盤,然後用1x2的骨牌去覆蓋整個棋盤。對於這個棋盤,一共有多少種不同的覆蓋方法呢?
舉個例子,對於長度為1到3的棋盤,我們有下面幾種覆蓋方式:

輸入

第1行:1個整數N。表示棋盤長度。1≤N≤100,000,000

輸出

第1行:1個整數,表示覆蓋方案數 MOD 19999997

樣例輸入
62247088
樣例輸出
17748018
【思路】矩陣快速冪

我們考慮在已經放置了部分骨牌(灰色)的情況下,下一步可以如何放置新的骨牌(藍色):

最右邊的一種情況是不可能發生的,否則會始終多一個格子沒有辦法放置骨牌。或者說灰色部分的格子數為奇數,不可能通過1x2個骨牌放置出來。
那麼通過對上面的觀察,我們可以發現:
在任何一個放置方案最後,一定滿足前面兩種情況。而灰色的部分又正好對應了長度為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;
}