1. 程式人生 > >【矩陣的十大經典題目中的第九】

【矩陣的十大經典題目中的第九】

Gym - 101635C Macarons
Step1 Problem:

給你 n*m 的矩陣,你可以填 1 * 1 的黑塊,或者 1 * 2 的黑塊,或者 2 * 1 的黑塊。求有多少填的方案可以將矩陣填滿。
資料範圍:
1 <= n <= 8, 1 <= m <= 1e18.

Step2 Ideas:

感謝 dq 給我講解了一波,發現了一篇很棒的部落格
前置技能:你需要將經典題目 8 先學會了。
這是你就知道你需要構造一個狀態轉移矩陣。
我們可以列舉第 i 列所有黑塊所能產生的樣子,然後將第 i 列填滿所構成的第 i+1 列的狀態,就是所列舉的第 i 列狀態能到達第 i+1 列狀態。
核心注意點:所填黑塊必須和第 i+1 列有關,不然該狀態就不是我們所列舉想要的狀態了。部落格裡面有講。

Step3 Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1<<8;
const int MOD = 1e9;
struct node
{
    ll a[N][N];
};
node c;
int n;
void dfs(int u, int now, int nex)
{
    if(u == n) {
        c.a[now][nex]++;//now 狀態 能到達 nex 狀態
        return ;
    }
    if(now>>u&1) {//當前位置有黑塊
        dfs(u+1, now, nex);//不填
        dfs(u+1, now, nex|(1<<u));//填 1 * 1
        if(u+2<=n && (now&(1<<(u+1)))) dfs(u+2, now, (nex|(1<<u))|(1<<(u+1))); // 下一個位置也有黑塊,可以直接對第 i+1 列填 2*1.
    }
    else {
        dfs(u+1, now, nex|(1<<u));//當前位置為空得填滿,填 1*2.
    }
}
node mul(node x, node y)
{
    node ans;
    memset(ans.a, 0, sizeof(ans.a));
    int m = (1<<n);
    for(int k = 0; k < m; k++)
    {
        for(int i = 0; i < m; i++)
        {
            if(x.a[i][k])
            for(int j = 0; j < m; j++)
            {
                ans.a[i][j] += x.a[i][k]*y.a[k][j];
                ans.a[i][j] %= MOD;
            }
        }
    }
    return ans;
}
node Pow(node x, ll m)
{
    node ans;
    memset(ans.a, 0, sizeof(ans.a));
    for(int i = 0; i < (1<<n); i++) ans.a[i][i] = 1;
    while(m)
    {
        if(m&1) ans = mul(ans, x);
        x = mul(x, x);
        m >>= 1;
    }
    return ans;
}
int main()
{
    ll m;
    scanf("%d %lld", &n, &m);
    memset(c.a, 0, sizeof(c.a));
    for(int i = 0; i < (1<<n); i++)
        dfs(0, i, 0);
    node ans = Pow(c, m);
    printf("%lld\n", ans.a[(1<<n)-1][(1<<n)-1]);//輸出狀態全填到狀態全填, m 列的方案數。
    return 0;
}

ACM-ICPC 2018 焦作賽區網路預賽 L. Poor God Water
Step1 Problem:

有 n 個位置,每個位置可以填 0, 1, 2.
相鄰三個數不能滿足如下幾個形式:
0, 0, 0
1, 1, 1
2, 2, 2
2, 1, 2
2, 0, 2
1, 2, 0
0, 1, 2
問你填滿 n 位的方案數。

Step2 Ideas:

構造狀態轉移矩陣
列舉兩位的所有狀態,多加一位如果合法,就得到了兩位狀態到兩位狀態的轉移。

Step3 Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 12;
const int MOD = 1e9+7;
struct node
{
    ll a[N][N];
};
node c, ans;
node mul(node x, node y)
{
    node ans;
    memset(ans.a, 0, sizeof(ans.a));
    for(int k = 0; k < 9; k++)
    {
        for(int i = 0; i < 9; i++)
        {
            if(x.a[i][k])
            for(int j = 0; j < 9; j++)
            {
                ans.a[i][j] += x.a[i][k]*y.a[k][j];
                ans.a[i][j] %= MOD;
            }
        }
    }
    return ans;
}
node Pow(node x, ll m)
{
    node ans;
    memset(ans.a, 0, sizeof(ans.a));
    for(int i = 0; i < 9; i++) ans.a[i][i] = 1;
    while(m)
    {
        if(m&1) ans = mul(ans, x);
        x = mul(x, x);
        m >>= 1;
    }
    return ans;
}
int ok[10] = {111, 222, 0, 120, 21, 212, 202};
void init()
{
    memset(c.a, 0, sizeof(c.a));
    for(int i = 0; i < 3; i++)
    {
        for(int j = 0; j < 3; j++)
        {
            for(int k = 0; k < 3; k++)
            {
                int t = i*100+j*10+k, k1;
                for(k1 = 0; k1 < 7; k1++) {
                    if(t == ok[k1]) break;
                }
                if(k1 == 7) c.a[i*3+j][j*3+k]++;//如果合法
            }
        }
    }
}
int main()
{
    int T;
    ll n;
    scanf("%d", &T);
    init();
    while(T--)
    {
        scanf("%lld", &n);
        if(n < 3) {
            if(n == 1) printf("3\n");
            else printf("9\n");
            continue;
        }
        ans = Pow(c, n-2);//矩陣是兩位的狀態到新的兩位的狀態,預設有兩位了。
        ll Ans = 0;
        for(int i = 0; i < 9; i++)
        {
            for(int j = 0; j < 9; j++)
            {
                Ans += ans.a[i][j], Ans%=MOD;
            }
        }
        printf("%lld\n", Ans);//因為矩陣轉移都是合法狀態,所以是所有狀態的和。
    }
    return 0;
}