2015年第六届蓝桥杯 - 省赛 - C/C++大学A组 - I. 垒骰子
壘骰子
賭圣atm晚年迷戀上了壘骰子,就是把骰子一個壘在另一個上邊,不能歪歪扭扭,要壘成方柱體。
 經過長期觀察,atm 發現了穩定骰子的奧秘:有些數字的面貼著會互相排斥!
 我們先來規范一下骰子:1 的對面是 4,2 的對面是 5,3 的對面是 6。
 假設有 m 組互斥現象,每組中的那兩個數字的面緊貼在一起,骰子就不能穩定的壘起來。
 atm想計算一下有多少種不同的可能的壘骰子方式。
 兩種壘骰子方式相同,當且僅當這兩種方式中對應高度的骰子的對應數字的朝向都相同。
 由于方案數可能過多,請輸出模 10^9 + 7 的結果。
不要小看了 atm 的骰子數量哦~
「輸入格式」
 第一行兩個整數 n m
 n表示骰子數目
 接下來 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
資源約定:
 峰值內存消耗 < 256M
 CPU消耗 < 2000ms
請嚴格按要求輸出,不要畫蛇添足地打印類似:“請您輸入…” 的多余內容。
所有代碼放在同一個源文件中,調試通過后,拷貝提交該源碼。
注意: main函數需要返回0
 注意: 只使用ANSI C/ANSI C++ 標準,不要調用依賴于編譯環境或操作系統的特殊函數。
 注意: 所有依賴的函數必須明確地在源文件中 #include , 不能通過工程設置而省略常用頭文件。
提交時,注意選擇所期望的編譯器類型。
Ideas
求解所有方案數問題,一般都是采用DFS搜索的方法,如果層數太深導致超時的話可以考慮DP。
題目中說0 < n <= 10^9,而且這可是倒數第二道大題,盲猜DFS應該會超時,所以考慮DP。
題目中又說“兩種壘骰子方式相同,當且僅當這兩種方式中對應高度的骰子的對應數字的朝向都相同”,也就是說,我們確定一個骰子的位置,其實只需要確定它上面的數字,側面的4個數字是可以轉的。
那么對于最頂部的骰子,定義為第1個骰子,我們有 6 種選擇,6個數字都可以朝上,然后繼續搜索下面的第2個骰子,但搜索的時候有一個要求,第2個骰子朝上的數字不能跟第1個骰子朝下的數字相沖突。
以此類推,這就是遞歸的調用過程,那么遞推,也就是DP,其實就是遞歸的逆向過程,我們需要從下往上考慮。
首先需要定義一個dp數組,dp[i][j]表示第i層j數字朝上的穩定方案數。
對于最下面一層,有6種情況,每一個面都可以朝上,所以dp[n][j]=1。
對于倒數第二層,假設1和2沖突,那么當倒數第二層1朝上時,1的對面是4,而4并不跟其它數字沖突,說明倒數第二層的骰子1朝上時可以壘在倒數第一層的所有情況的骰子上,記為6。
而當倒數第二層骰子4朝上時,4的對面是1,1跟2沖突,說明倒數第二層骰子4朝上時,不能壘在倒數第一層2朝上的骰子上,記為5。
就這么一層一層往上推就可以了,我們發現第i層的骰子方案數只依賴于第 i - 1 層的骰子,所以可以用滾動數組的方法壓縮DP矩陣。
最后我們只需要把推到第1層的所有方案數加起來,然后乘以4的n次方就OK了。
考慮完了限制情況之后,我們需要定義一些輔助變量和函數:
(寫完DP之后:MD,DP都超時,人傻了)
看了一些題解說要用快速冪:https://www.bilibili.com/video/BV1g7411d74c?p=16
回來再補,今天進度趕不上了。
Code
C++
遞歸做法
#define MOD 1000000007#include <iostream>using namespace std; int n, m; int op[7]; bool conflict[7][7];/*** 上一層定好了朝上的數字為up的情況下,壘好cnt個骰子的方案數* @param up* @param cnt* @return*/ long long int f(int up, int cnt) {if (cnt == 0)return 4;long long ans = 0;for (int upp = 1; upp <= 6; ++upp) {if (conflict[op[up]][upp])continue;ans =(ans+ f(upp, cnt - 1))%MOD;}return ans; }void init() {op[1] = 4;op[4] = 1;op[2] = 5;op[5] = 2;op[3] = 6;op[6] = 3; }int main(int argc, const char *argv[]) {init();scanf("%d %d", &n, &m);for (int i = 0; i < m; ++i) {int x, y;scanf("%d %d", &x, &y);conflict[x][y] = true;conflict[y][x] = true;}long long ans = 0;for (int up = 1; up <= 6; ++up) {ans = (ans + 4 * f(up, n - 1)) % MOD;}printf("%lli", ans);return 0; }動態規劃做法
#define MOD 1000000007#include <map> #include <vector> #include <iostream>using namespace std;long long dp[2][7];//dp[i][j]表示有i層,限定朝上的數字為j的穩定方案數 int n, m; bool conflict[7][7]; map<int, int> op;void init() {op[1] = 4;op[4] = 1;op[2] = 5;op[5] = 2;op[3] = 6;op[6] = 3; }int main(int argc, const char *argv[]) {init();scanf("%d %d", &n, &m);for (int i = 0; i < m; ++i) {int a, b;scanf("%d %d", &a, &b);conflict[a][b] = true;conflict[b][a] = true;} // 輸入完成for (int j = 1; j <= 6; ++j) {dp[0][j] = 1;}int cur = 0; // 迭代層數for (int level = 2; level <= n; ++level) {cur = 1 - cur; // 嘗試將6個面放在當前一層朝上的方向for (int j = 1; j <= 6; ++j) {dp[cur][j] = 0; // 將與op[j]不沖突的上一層格子里面的數累加起來for (int i = 1; i <= 6; ++i) {if (conflict[op[j]][i])continue;//沖突的面朝上是不可取的dp[cur][j] = (dp[cur][j] + dp[1 - cur][i]) % MOD;}}}long long sum = 0;for (int k = 1; k <= 6; ++k) {sum = (sum + dp[cur][k]) % MOD;}// 快速冪,求4的n次方long long ans = 1;long long tmp = 4;long long p = n;while (p != 0) {if (p & 1 == 1) ans = (ans * tmp) % MOD;tmp = (tmp * tmp) % MOD;p >>= 1;}printf("%d\n", (sum * ans) % MOD);return 0; }矩陣冪做法
#define MOD 1000000007 typedef long long LL;#include <map> #include <vector> #include <iostream>using namespace std;int n, m; map<int, int> op;void init() {op[1] = 4;op[4] = 1;op[2] = 5;op[5] = 2;op[3] = 6;op[6] = 3; }struct M {LL a[6][6];M() { // memset(a,1, sizeof(a));for (int i = 0; i < 6; ++i) {for (int j = 0; j < 6; ++j) {a[i][j] = 1;}}} }; M mMultiply(M m1,M m2){M ans;for (int i = 0; i < 6; ++i) {for (int j = 0; j < 6; ++j) {ans.a[i][j]=0;for (int k = 0; k < 6; ++k) {ans.a[i][j]=(ans.a[i][j]+m1.a[i][k]*m2.a[k][j])%MOD;}}}return ans; } //求M的k次方 M mPow(M m, int k) {M ans;//單位矩陣 // 對角線為1,其余為0for (int i = 0; i < 6; ++i) {for (int j = 0; j < 6; ++j) {if (i == j)ans.a[i][j] = 1;elseans.a[i][j] = 0;}}while (k != 0) {if ((k & 1) == 1) {ans = mMultiply(ans,m);}m=mMultiply(m,m);k >>= 1;//向右移動1位}return ans; }int main(int argc, const char *argv[]) {init();scanf("%d %d", &n, &m);M cMatrix;//沖突矩陣for (int i = 0; i < m; ++i) {int a, b;scanf("%d %d", &a, &b);//完善沖突矩陣cMatrix.a[op[a] - 1][b - 1] = 0;cMatrix.a[op[b] - 1][a - 1] = 0;}M cMatrix_n_1 = mPow(cMatrix, n - 1);//沖突矩陣的n-1次方LL ans=0;for (int j = 0; j < 6; ++j) {for (int i = 0; i < 6; ++i) {ans=(ans+cMatrix_n_1.a[i][j])%MOD;}}// 快速冪,求4的n次方long long t = 1;long long tmp = 4;long long p = n;while (p != 0) {if (p & 1 == 1) t = (t * tmp) % MOD;tmp = (tmp * tmp) % MOD;p >>= 1;}printf("%lli",ans*t%MOD);return 0; }Python
DP
if __name__ == '__main__':MOD = 1000000007opposites = {1: 4, 4: 1, 2: 5, 5: 2, 3: 6, 6: 3}n, m = map(int, input().split())conflict = [[False] * 7 for _ in range(7)]for _ in range(m):x, y = map(int, input().split())conflict[x][y] = Trueconflict[y][x] = Truedp = [[1] * 7 for _ in range(2)] # 初始化dp數組的時候順便把第1層的方案數也初始化了cur = 0 # 當前處理層數for level in range(1, n):cur = 1 - cur # 取反表示滾動處理下一層for j in range(1, 7): # 嘗試將 6 個面放在當前層朝上的方向dp[cur][j] = 0for i in range(1, 7):if not conflict[opposites[j]][i]: # 保證不沖突dp[cur][j] = (dp[cur][j] + dp[1 - cur][i]) % MODans = 0for i in range(1, 7):ans = (ans + dp[cur][i]) % MODfor _ in range(n):ans = (4 * ans) % MODprint(ans)在線評測:https://www.acwing.com/problem/content/description/1219/
總結
以上是生活随笔為你收集整理的2015年第六届蓝桥杯 - 省赛 - C/C++大学A组 - I. 垒骰子的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 征战蓝桥 —— 2014年第五届 ——
- 下一篇: 征战蓝桥 —— 2015年第六届 ——
