1. 程式人生 > >壘骰子(25 point(s))(矩陣快速冪+快速冪)

壘骰子(25 point(s))(矩陣快速冪+快速冪)

壘骰子(25 point(s))

賭聖atm晚年迷戀上了壘骰子,就是把骰子一個壘在另一個上邊,不能歪歪扭扭,要壘成方柱體。經過長期觀察,atm 發現了穩定骰子的奧祕:有些數字的面貼著會互相排斥!我們先來規範一下骰子:1 的對面是 4,2 的對面是 5,3 的對面是 6。假設有 m 組互斥現象,每組中的那兩個數字的面緊貼在一起,骰子就不能穩定的壘起來。 atm想計算一下有多少種不同的可能的壘骰子方式。兩種壘骰子方式相同,當且僅當這兩種方式中對應高度的骰子的對應數字的朝向都相同。由於方案數可能過多,請輸出模 10^9 + 7 的結果。

不要小看了 atm 的骰子數量哦~

輸入格式:

第一行兩個整數 n mn表示骰子數目接下來 m 行,每行兩個整數 a b ,表示 a 和 b 數字不能緊貼在一起。

輸出格式:

一行一個數,表示答案模 10^9 + 7 的結果。

輸入樣例:

2 1
1 2

輸出樣例:

544

「資料範圍」

對於 30% 的資料:n <= 5
對於 60% 的資料:n <= 100
對於 100% 的資料:0 < n <= 10^9, m <= 36

注意

所有程式碼放在同一個原始檔中,除錯通過後,拷貝提交該原始碼。

注意: main函式需要返回0注意: 只使用ANSI C/ANSI C++ 標準,不要呼叫依賴於編譯環境或作業系統的特殊函式。注意: 所有依賴的函式必須明確地在原始檔中 #include , 不能通過工程設定而省略常用標頭檔案。

提交時,注意選擇所期望的編譯器型別。


講的非常的詳細。

這裡如果下面想要看懂,請先看原部落格講解。

但是我在思考具體矩陣相乘的選擇過程,和每個矩陣行列所代表的實際含義時,根據原博主對complict矩陣的定義,所得到的含義是相互矛盾的,雖然最終提交結果正確但我卻解釋不通,而我感覺將其含義進行以下更改更好理解

單行矩陣Count[1][ j ]來記錄高度為N時, 頂面為j的總方案數.這個定義沒有問題

原博主對complict[][]矩陣定義:

1 . Complict[ i ][ k ] 表示: 有一個點數為i的面, 它的反面與點數為k的面存在衝突.

上述定義意味著, 如果1和2存在衝突, 那麼Compact[4][2] 和 Compact[5][1] 都為0 , 因為4的反面是1, 5的反面是2.

而我認為該矩陣正確的定義應該為:Complict[i][k]表示,點數為k的面的反面和點數為i的面是否存在衝突!!!!!

下面舉個例子進行講解                           k

                                                       i    1     1     1     1     1     1

 j   1     2     3      4     5     6                1     1     1     1     1     1     (該矩陣只是隨便舉得例子,可能不合理)

(___ , ___ , ___ , ___ , ___ , ___)  x        1     1     1     1     1     1

                                                            1     0     1     1     1     1

                                                               1     1     1     1     1

                                                            1     1     1     1     1     1

Count * Complict = newCount;就是一個選擇的過程

因為Count記錄的是當前高度頂面為j的情況下的總方案數,那麼我們的下一個高度即

newCount同樣是頂面為j情況下的總方案數,那麼我們選擇時也就是判斷是否符合題意時,就需要判斷j的對面是否和我們列舉的面(上一個高度的頂面)有衝突,因此這就是Complict[ i ][ k ]矩陣記錄的是點數為i的面, 它的反面與點數為k的面是否存在衝突,而不是點數為i的面和點數為k的面是否存在衝突。

根據上圖選擇矩陣的列k最終會變成當前頂面1-6,所以k就是新的頂面,j是舊的頂面,而根據矩陣的乘法法則的理解,我們可以把後面選擇矩陣的i和j看成相同含義,即都是舊的頂面。所以相乘的選擇過程實際就是,當新的頂面為k時,列舉上一個高度時的每一個頂面,如果k的對立面和上一個高度時的我們列舉的頂面衝突就不加那個情況下的個數否則加上,這樣就說得通了。

舉個例子:上圖,前面矩陣(就一行)乘後面選擇矩陣的第一列時,也就是我們在求再放一個骰子讓他頂面是1,這是這列的0,1數字就代表1的對立面數字和1-6是否衝突,也就是列舉的和上一個骰子每種情況下是否衝突,上圖紅色的0說明1的對立面和5是衝突的,說明上一個骰子頂面是5的情況不能加上,其他的都可以加,這樣我們就得到了新的頂面為1的所有情況,其他的相信大家也就好理解了。

code:

//LQ(7)壘骰子
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int MAXN = 8;
const ll MOD = 1e9 + 7;
int parner[7] = {0,4,5,6,1,2,3};
int n,m;

struct matrix{
    ll con[MAXN][MAXN];
    matrix(){
        for(int i = 0; i < MAXN; i++){
            for(int j = 0; j < MAXN; j++){
                con[i][j] = 0;
            }
        }
    }
};

matrix mul(matrix a,matrix b){//矩陣乘法
    matrix ans;
    for(int i = 1; i <= 6; i++){
        for(int j = 1; j <= 6; j++){
            if(a.con[i][j]){
                for(int k = 1; k <= 6; k++){
                    ans.con[i][k] =(ans.con[i][k] + (a.con[i][j] * b.con[j][k] % MOD)) % MOD;
                }
            }
        }
    }
    return ans;
}

matrix m_pow(matrix a,int b){//矩陣快速冪
    matrix ans;
    for(int i = 1; i <= 6; i++){
        ans.con[i][i] = 1;
    }
    while(b){
        if(b & 1){
            ans = mul(ans,a);
        }
        a = mul(a,a);
        b >>= 1;
    }
    return ans;
}

ll q_pow(ll a,ll b,ll c){//常數快速冪取模
    ll ans = 1;
    while(b){
        if(b & 1){
            ans = ans * a % c;
        }
        a = a * a % c;
        b >>= 1;
    }
    return ans;
}
int main(){
    matrix counts;
    for(int i = 1; i <= 6; i++){
        counts.con[1][i] = 1;
    }
    matrix complict;
    for(int i = 1; i <= 6; i++){
        for(int j = 1; j <= 6; j++){
            complict.con[i][j] = 1;
        }
    }
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= m; i++){
        int a,b;
        scanf("%d%d",&a,&b);
        complict.con[a][parner[b]] = complict.con[b][parner[a]] = 0;//重點就是這裡的儲存問題,正確儲存方式
        //若按照原作者定義應該這樣儲存complict.con[parner[a]][b] = complict.con[parner[b]][a] = 0,雖然提交仍然正確,道理上確實講不通的。
    }
    counts = mul(counts,m_pow(complict,n-1));
    ll ans = 0;
    for(int i = 1; i <= 6; i++){
        ans += counts.con[1][i];
    }
    ll times = q_pow(4,n,MOD);//最後每一個頂面可以水平四個方向轉,所以還要乘4^n
    ans = ans * times % MOD;
    printf("%lld\n",ans);
    return 0;
}