【矩陣的十大經典題目中的第九】
阿新 • • 發佈:2018-12-13
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; }