【題解】 英雄會-第五屆在線編程大賽月賽第二題:走格子

題目詳情

我們有一個兩行n列格子的棋盤,你可以從任何位置出發。每次你可以沿着上下左右以及對角線的方向走一格(不能出去),求有多少條可能的哈密爾頓路?(即所有的格子只經過一次的路。)

例如:

a b c

d e f

一條可能的路徑是b,f,c,e,d,a

輸入格式:

多組數據,每組數據1行,包含一個正整數n表示列數。 (n <= 1000)

輸出格式:

每組數據輸出一行包含一個整數,可能的路徑條數。結果比較大,輸出對10^9 + 7的結果

答題說明

輸入樣例

1

2

3

輸出樣例:

2

24

96

這道題需要對題目抽象一下,然後抽象出的模型進行遞推總結規律,最後還用到同餘定理。

首先題目中給出了2 x n的方格,要求是求出有多少個哈密頓路。有意思的是,可以沿着上下左右以及對角線的方向走一格。
模擬一下,假設有個小人從某一個起點出發,在不看縱方向,只看橫方向,那麼對於2 x n這樣的方格,放置位置就會有n種選擇,假設放在從左往右起第i個格子組上,那麼就會有2种放置方式(在上邊或放在下面)那麼向左或者向右移動的話,也會有兩種移動方式(譬如向右吧,就會有移動到右上或移動到右下兩種移動方式)
換句話說,對於 2 x n這樣的方格,遍歷所有格子的移動可能性爲2的n次方。
爲了方便問題的解決,我們需要屏蔽掉移動可能性數,於是做這樣的抽象:
將2 x n的方格抽象爲n維向量,定義向量中的第i維內的數的意義爲:已經路過了從左向右第i組格子幾次。
那麼初始情況下這個向量一定是個0向量,且向量中的元素一定是小於等於2的非負整數。
考慮樣例:
n=1時,有2種選擇,這個樣例可以忽略
n=2時,有24種選擇,我們將24除以2的二次方得到6,6即爲在我們抽象的出的模型內的路徑數。
n=3時,有96種選擇,我們將96除以2的三次方得到12,12即爲在我們抽象出的模型內的路徑數。

n=1時,是一個一維向量,所以不用過多考慮
n=2時,是一個二維向量,我們需要對它進行進一步討論
如果將移動指針初始點放置在1位置,那麼這三條路徑爲:
[1,0] -> [2,0] -> [2,1] -> [2,2]
[1,0] -> [1,1] -> [2,1] -> [2,2]
[1,0] -> [1,1] -> [1,2] -> [2,2]
由於是對稱的,所以移動指針初始點放置在2位置,也爲三條路徑,不贅述。
仔細觀察這三種路徑,我們可以將其分爲兩類:一類終點在2位置,另一類重點在原位置。
我們推廣一下,對於一個n維向量:
第一類:
[1,0,…0] -> [2,0,…,0] ->[2,1,0,…,0]->…
[1,0,…,0] -> [1,1,0,…,0] -> [2,1,0,…,0] -> [2,2,0,…,0] -> [2,2,1,0,…,0] -> …
第二類:
[1,0,…,0] -> [1,1,0,…,0] -> [1,1,1,0,…,0] -> [1,1,…,1] -> [1,1,…,1,2] -> [1,1,…,1,2,2] -> [2,…,2]
只能這麼走,因爲在一開始就暫停在原地,就是第一類的第一種情況,在中途暫停或向後走,則將不滿足題目要求。

我們假設有這樣一個函數f(n),其意義爲一個n維向量,若起點在第1位置,則可能的路徑數。
那麼我們可以總結得到這樣一個遞推公式:
f(n) = f(n-1) + f(n-2) + 1
當然這是邊界情況,那如果起點是非邊界呢?
那麼移動選擇僅可能是向左或者向右而不能暫留。不論選擇向左還是選擇向右,必須要回到起點,考慮上述第一類移動方式和第二類移動方式,那麼無論最初的向左移動還是向右移動,這一半的路徑數爲1(在抽象模型情況之上是這樣噠)。那麼剩下的格子的移動方式數就雷同於邊界情況了,換句話說,初始移動點爲非邊界情況可以通過化爲兩個初始移動點位邊界情況來得到。
於是對於起點爲從左向右第i個格子(i != 1 且 i != n),可以得到這樣的公式:
g(i) = f(i-1) + f(n - i)

爲了驗證真僞,通過n=3來進行驗證。
n=3
f(1) = 1;
f(2) = 3;
f(3) = f(1) + f(2) + 1 = 5
存在兩個邊界和一箇中間點,中間點計算結果爲
g(2) = f(1) + f(1) = 2
那麼最終結果爲 2 + 5 * 2 = 12
再將12乘2的三次方:12 * 8 = 96
問題解決。

實現代碼如下:

#include <iostream>
#define MOD(i) (i) % 1000000007
long long border[1010] = {0};
int bValid = 0;
long long modIndex[1010] = {0};
int mValid = 0;

void init(){
    border[0] = 0;
    border[1] = 1;
    border[2] = 3;
    border[3] = 5;
    bValid = 3;

    modIndex[0] = 1;
    modIndex[1] = 2;
    modIndex[2] = 4;
    modIndex[3] = 8;
    mValid = 3;
}

long long GetBorderModValue(int i){
    while(bValid < i) border[++bValid] = MOD(border[bValid-1]+border[bValid-2]+1);
    return border[i];
}

long long GetIndexModValue(int i){
    while(mValid < i) modIndex[++mValid] = MOD(modIndex[mValid-1]*2);
    return modIndex[i];
}

int main(){
    using namespace std;
    init();
    int n;
    while(cin >> n){
        long long GMidValue = 0;
        for(int i=1; i<=n;i++){
            if(i == 1 || i == n) GMidValue = MOD(GMidValue + GetBorderModValue(n));
            else GMidValue = MOD(GMidValue + MOD(GetBorderModValue(i-1) + GetBorderModValue(n-i)));
        }
        cout << MOD(GMidValue * GetIndexModValue(n)) << endl;
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章