1. 程式人生 > >【矩陣乘】【DP】【codevs 1305】Freda的道路

【矩陣乘】【DP】【codevs 1305】Freda的道路

1305 Freda的道路

 時間限制: 1 s
 空間限制: 128000 KB
 題目等級: 大師 Master

題目描述 Description

Freda要到Rainbow的城堡去玩了。我們可以認為兩座城堡位於同一條數軸上,Freda的城堡座標是0,Rainbow的城堡座標是N。正常情況下,Freda會朝著同一個方向(即Rainbow的城堡相對於Freda的城堡的方向)走若干步之後來到Rainbow的城堡,而且步長都為1或2。可是,今天Freda在途中遇見了來自上海的小貓Resodo,驚奇之下,居然有一步走反了方向!不過,Freda並沒有神智不清,它只有一步走反了方向, 而且這一步的步長也是1或2. 同時,Freda並不會路過Rainbow的城堡而不停下來。當然,Freda是在途中遇到Resodo的,所以它不會在 自己家門口就走錯方向。
舉個例子,如果Rainbow的城堡座標是3,那麼下面兩條路徑是合法的:

0->1->2->1->3
0->1->-1->1->3

當然,還有其它的合法路徑。下面這些路徑則是不合法的:

0->-1->1->3 (Freda不可能第一步就走錯方向)
0->1->3(Freda一定是有一步走錯方向的)
0->2->1->0->2->3(Freda只有一步是走錯方向的)
0->-1->0->3(Freda每步的長度一定是1或2)
0->1->2->4->3(Freda不會越過Rainbow的城堡再回來)
0 -> 1 -> 2 -> 3 -> 2 -> 3(Freda一旦到達了Rainbow的城堡,就會停下來)

你現在需要幫助Freda求出,它一共有多少種方法能夠到達Rainbow的城堡呢?

輸入描述 Input Description

一行一個整數N,表示Rainbow城堡的座標

輸出描述 Output Description

一行一個整數,表示Freda到Rainbow城堡的不同路徑數。
由於這個數字可能很大,你只需要輸出它mod 1000000007的結果。

樣例輸入 Sample Input

2

樣例輸出 Sample Output

5

資料範圍及提示 Data Size & Hint
對於第一組樣例,如下5條路徑是合法的:

0->1->0->2
0->1->-1->0->1->2
0->1->-1->0->2
0->1->0->1->2
0->1->-1->1->2

資料範圍與約定

對於10%的資料,N<=20.
對於70%的資料,N<=1000.
對於90%的資料,N<=1000000.
對於100%的資料,N<=10^15.

題解:

因為是討論路徑個數,所以首先想到的是dp。
設f[i]表示在不回頭的情況下到達的路徑數,g[i]表示在一次回頭的情況下到達的路徑數。
由題意可知:
f[i]=f[i-2]+f[i-1](當前只能從兩步前或是一步前轉移來),即Fibonacci數列。
接下來考慮g[i],發現g[i]由四種情況轉移過來,即:
1>g[i-2](在轉過的情況下,由兩步前走來)
2>g[i-1](在轉過的情況下,由一步前走來)
3>f[i+1](一步以後轉回來)
4>f[i+2](兩步以後轉回來)
於是可得轉移方程:
g[i]=g[i-2]+g[i-1]+f[i+1]+f[i+2]
裸的dp可得90 。。。最後一個點是極限資料,DP做不到。於是想到了矩陣乘。
對於以下兩個矩陣:

A0,1,0,01,1,1,10,0,0,10,0,1,1
Bg[n2]g[n1]f[n+1]f[n+2]
g[n]可以這樣求得:
AB=Cg[n1]g[n]f[n+2]f[n+3]
於是我們可以這樣求解g[n]:
An2g[1]g[2]f[4]f[5]

Code:
DP:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define p 1000000007LL
using namespace std;
long long n,f[1000010],g[1000010];
int main(){
    scanf("%d",&n);
    if (n==1){
        printf("0\n");
        return 0;
    }
    f[1]=f[2]=g[0]=g[1]=1;
    for (int i=3; i<=n+1; i++)
        f[i]=(f[i-1]+f[i-2])%p;
    for (int i=2; i<=n+1; i++)
        g[i]=(g[i-2]+g[i-1]+f[i+1]+f[i+2])%p;
    printf("%d\n",g[n+1]);
    return 0;
}

矩陣乘:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define p 1000000007LL
using namespace std;
long long A[4][4]={{0,1,0,0},{1,1,1,1},{0,0,0,1},{0,0,1,1}};
long long B[4][4],C[4][4];
int main(){
    long long n; bool f=true;
    scanf("%lld",&n);
    if (n<=2){
        if (n==1) printf("0\n");
        else printf("5\n");
        return 0;
    }
    n-=2;
    while (n){
        if (n&1){
            if (f){
                for (int i=0; i<4; i++)
                    for (int j=0; j<4; j++)
                        B[i][j]=A[i][j];
                f=false;
            }
            else {
                memset(C,0,sizeof(C));
                for (int i=0; i<4; i++)
                    for (int j=0; j<4; j++)
                        for (int k=0; k<4; k++)
                            C[i][j]=(C[i][j]+B[i][k]*A[k][j])%p;
                for (int i=0; i<4; i++)
                    for (int j=0; j<4; j++)
                        B[i][j]=C[i][j];
            }
        }
        n>>=1;
        memset(C,0,sizeof(C));
        for (int i=0; i<4; i++)
            for (int j=0; j<4; j++)
                for (int k=0; k<4; k++)
                    C[i][j]=(C[i][j]+A[i][k]*A[k][j])%p;
        for (int i=0; i<4; i++)
            for (int j=0; j<4; j++)
                A[i][j]=C[i][j];
    }
    long long ans=(B[1][1]*5%p+B[1][2]*5%p+B[1][3]*8%p)%p;
    printf("%lld\n",ans);
    return 0;
}