象棋走位

前言


無論國際象棋還是中國象棋中,馬的走位都是先橫著或直著走一格,然后再斜著走一個對角線,俗稱“馬走日”。
那么在一個特定棋盤中馬在有限步數下有多少種走法?今天就研究一個關于“馬走日”的問題:騎士游歷。

國際象棋

題目


時間限制:10000ms
單點時限:1000ms
內存限制:256MB
描述
在8x8的國際象棋棋盤上給定一只騎士(俗稱“馬”)棋子的位置(R, C),小Hi想知道從(R, C)開始移動N步一共有多少種不同的走法。

輸入
第一行包含三個整數,N,R和C。

對于40%的數據, 1 <= N <= 1000000

對于100%的數據, 1 <= N <= 1000000000 1 <= R, C <= 8

輸出
從(R, C)開始走N步有多少種不同的走法。由于答案可能非常大,你只需要輸出答案模1000000007的余數。

樣例輸入

2 1 1

樣例輸出

12

分析


首先,比較直觀的思路是嚴格按照規則,每走一步記錄狀態,每個狀態下馬走的路徑最多8條,然后迭代查詢每條下面路徑下的子路徑,最后求的結果。這種思路是指數型增長的,并且迭代調用函數,可能沒計算幾步計算機就無能為力了。

既然迭代不行,就可以用動態規劃,記錄每一點每一步狀態下可能的路徑,但是這個方法在 N = 1000000000的時候還是會超時。

因為N會很大,如果按照直觀思路肯定是無法解,要么找到循環規律之類,要們降低N計算次數。

對于本題,我們看到其實馬走的路徑彼此之間是唯一聯系的,可以看作是連通圖,然后連通圖的鄰接矩陣A乘法就是路徑狀態的記錄。比如在8x8對于點(1,1),初始狀態與他相連的點是(2,3),(3,2),鄰接矩陣(1,1)對應的行狀態就是[0,0,...,1(10),0,..0...1(17),0],所以如果N=1,總路徑就是2。N=2,我們就需要進行矩陣乘法AxA,(1,1)對應的狀態會變成[2,0,1,0,1,0...0,1(11),0...0,1(16),0..0,1(20),0...0,1(25),0,2,0..0,1(32),0,1,0...0],總路徑就是12。

初始聯通圖:

void initGrid()
{
    for (int i = 1; i <= 8; i++) {
        for (int j = 1; j <= 8; j++) {
            int index = (i-1)*8+j-1;
            if (j + 2 <= 8 && i + 1 <= 8) {
                int temp = i*8+j+1;
                grid[index][temp] = 1;
            }
            if (j + 1 <= 8 && i + 2 <= 8) {
                int temp = (i+1)*8+j;
                grid[index][temp] = 1;
            }
            if (j + 2 <= 8 && i - 1 >= 1) {
                int temp = (i-2)*8+j+1;
                grid[index][temp] = 1;
            }
            if (j + 1 <= 8 && i - 2 >= 1) {
                int temp = (i-3)*8+j;
                grid[index][temp] = 1;
            }
            if (j - 1 >= 1 && i - 2 >= 1) {
                int temp = (i-3)*8+j-2;
                grid[index][temp] = 1;
            }
            if (j - 2 >= 1 && i - 1 >= 1) {
                int temp = (i-2)*8+j-3;
                grid[index][temp] = 1;
            }
            if (j - 1 >= 1 && i + 2 <= 8) {
                int temp = (i+1)*8+j-2;
                grid[index][temp] = 1;
            }
            if (j - 2 >= 1 && i + 1 <= 8) {
                int temp = i*8+j-3;
                grid[index][temp] = 1;
            }
        }
    }
}

有了上面的原理,我們求某點出發的路徑總數,只需要進行矩陣乘法就行了,但是N很大,如果直接計算還是不行,我們需要用到快速冪運算。其原理就是把指數看作二進制數,比如A5,5看作二進制數就是101,A5=A1x22xA0x21xA1x20,一個數記錄a=A2x,另一個數記錄結果res,遇到二進制數為1就res=resxa,計算次數降低到log2N。快速冪運算不僅適用于數字,矩陣其實也是一樣的。只是乘積算法不同,對于數字,res初始為1,矩陣初始就是Indentity Martrix。

void mul1() // res * grid
{
    long long temp[64][64];
    memset(temp, 0, sizeof(temp));
    for (int i = 0; i < 64; i++) {
        for (int j = 0; j < 64; j++) {
            for (int k = 0; k < 64; k++) {
                temp[i][j] += res[i][k] * grid[k][j];
                temp[i][j] %= m;
            }
        }
    }
    for (int i = 0; i < 64; i++) {
        for (int j = 0; j < 64; j++) {
            res[i][j] = temp[i][j];
        }
    }
}

void mul2() // grid * grid
{
    long long temp[64][64];
    memset(temp, 0, sizeof(temp));
    for (int i = 0; i < 64; i++) {
        for (int j = 0; j < 64; j++) {
            for (int k = 0; k < 64; k++) {
                temp[i][j] += grid[i][k] * grid[k][j];
                temp[i][j] %= m;
            }
        }
    }
    for (int i = 0; i < 64; i++) {
        for (int j = 0; j < 64; j++) {
            grid[i][j] = temp[i][j];
        }
    }
}

void Factor(long long n)
{
    for (int i = 0; i < 64; i++) {
        for (int j = 0; j < 64; j++) {
            if (i == j) {
                res[i][j] = 1;
            }
            else
            {
                res[i][j] = 0;
            }
        }
    }
    while (n) {
        if (n&1) {
            mul1();
        }
        mul2();
        n>>=1;
    }
}

結果


順利AC,但是效率還有提高的地方。


完整代碼:

//
//  main.cpp
//  騎士游歷
//
//  Created by Jiao Liu on 7/9/19.
//  Copyright ? 2019 ChangHong. All rights reserved.
//

#include <iostream>
#include <string.h>

using namespace std;

long long grid[64][64];
long long res[64][64];
long long m = 1000000007;
long long n,r,c;

void initGrid()
{
    for (int i = 1; i <= 8; i++) {
        for (int j = 1; j <= 8; j++) {
            int index = (i-1)*8+j-1;
            if (j + 2 <= 8 && i + 1 <= 8) {
                int temp = i*8+j+1;
                grid[index][temp] = 1;
            }
            if (j + 1 <= 8 && i + 2 <= 8) {
                int temp = (i+1)*8+j;
                grid[index][temp] = 1;
            }
            if (j + 2 <= 8 && i - 1 >= 1) {
                int temp = (i-2)*8+j+1;
                grid[index][temp] = 1;
            }
            if (j + 1 <= 8 && i - 2 >= 1) {
                int temp = (i-3)*8+j;
                grid[index][temp] = 1;
            }
            if (j - 1 >= 1 && i - 2 >= 1) {
                int temp = (i-3)*8+j-2;
                grid[index][temp] = 1;
            }
            if (j - 2 >= 1 && i - 1 >= 1) {
                int temp = (i-2)*8+j-3;
                grid[index][temp] = 1;
            }
            if (j - 1 >= 1 && i + 2 <= 8) {
                int temp = (i+1)*8+j-2;
                grid[index][temp] = 1;
            }
            if (j - 2 >= 1 && i + 1 <= 8) {
                int temp = i*8+j-3;
                grid[index][temp] = 1;
            }
        }
    }
}

void mul1() // res * grid
{
    long long temp[64][64];
    memset(temp, 0, sizeof(temp));
    for (int i = 0; i < 64; i++) {
        for (int j = 0; j < 64; j++) {
            for (int k = 0; k < 64; k++) {
                temp[i][j] += res[i][k] * grid[k][j];
                temp[i][j] %= m;
            }
        }
    }
    for (int i = 0; i < 64; i++) {
        for (int j = 0; j < 64; j++) {
            res[i][j] = temp[i][j];
        }
    }
}

void mul2() // grid * grid
{
    long long temp[64][64];
    memset(temp, 0, sizeof(temp));
    for (int i = 0; i < 64; i++) {
        for (int j = 0; j < 64; j++) {
            for (int k = 0; k < 64; k++) {
                temp[i][j] += grid[i][k] * grid[k][j];
                temp[i][j] %= m;
            }
        }
    }
    for (int i = 0; i < 64; i++) {
        for (int j = 0; j < 64; j++) {
            grid[i][j] = temp[i][j];
        }
    }
}

void Factor(long long n)
{
    for (int i = 0; i < 64; i++) {
        for (int j = 0; j < 64; j++) {
            if (i == j) {
                res[i][j] = 1;
            }
            else
            {
                res[i][j] = 0;
            }
        }
    }
    while (n) {
        if (n&1) {
            mul1();
        }
        mul2();
        n>>=1;
    }
}

int main(int argc, const char * argv[]) {
    memset(grid, 0, sizeof(grid));
    initGrid();
    cin>>n>>r>>c;
    Factor(n);
    long long ans = 0;
    long long index = (r-1)*8+c-1;
    for (int i = 0; i < 64; i++) {
        ans += res[index][i];
        ans %= m;
    }
    cout<<ans<<endl;
    return 0;
}

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom閱讀 2,715評論 0 3
  • 回溯算法 回溯法:也稱為試探法,它并不考慮問題規模的大小,而是從問題的最明顯的最小規模開始逐步求解出可能的答案,并...
    fredal閱讀 13,703評論 0 89
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些閱讀 2,042評論 0 2
  • 我不喜歡談錢,第一是因為自己賺的不多,只能算是貧農。第二,因為一旦談錢總是會有比較,誰賺的多,誰賺的少,誰家的錢多...
    宛若清風R閱讀 486評論 0 1
  • 大約兩個月前,我開始踐行微習慣,其中之一就是微運動。具體內容是,每天都要做平板支撐和俯臥撐。剛開始,平板支撐大約是...
    冰鋒冰鋒閱讀 888評論 0 16