垒骰子

垒骰子

赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。

经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!

我们先来规范一下骰子:1 的对面是 42 的对面是 53 的对面是 6

假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。

atm想计算一下有多少种不同的可能的垒骰子方式。

两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。

由于方案数可能过多,请输出模 109+7 的结果。

输入格式

第一行包含两个整数 n,m,分别表示骰子的数目和排斥的组数。

接下来 m 行,每行两个整数 a,b,表示 ab 数字不能紧贴在一起。

输出格式

共一个数,表示答案模 109+7 的结果。

数据范围

1n109,
1m36,
1a,b6

输入样例:

2 1
1 2

输出样例:

544

 

解题思路

  如果n的数据范围很小的话,可以用动态规划来做。

  当然,因为会有一些限制,比如有12不可以贴在一起,那么有些集合就会变成空集。例如有f(i,5),那么对应的f(i1,1)就是空集;有f(i,4),那么对应的f(i1,2)就是空集(12的对立面分别是45)。

  因此我们可以简化成f(i,j)=k=16ckf(i1,k),其中ck等于04,如果有限制条件约数那么就等于0,否则等于4

  这种做法的时间复杂度是O(n),由于n取到109,因此会超时。

  我们可以发现对于f(i1,j),有f(i1,j)=k=16ckf(i2,k),其中每一个系数即ck都与f(i,j)的相同,因为对于同一个j,他们的在每一层的限制都是一样的。因此可以抽象成矩阵相乘的形式。

  设有向量

F(i)=[f(i,1)f(i,2)f(i,3)f(i,4)f(i,5)f(i,6)]

F(i1)=[f(i1,1)f(i1,2)f(i1,3)f(i1,4)f(i1,5)f(i1,6)]

设有矩阵

A=[c11c12c13c14c15c16c21c22c23c24c25c26c31c32c33c34c35c36c41c42c43c44c45c46c51c52c53c54c55c56c61c62c63c64c65c66]

其中cij根据是否有限制取04

  可以发现有F(i)=F(i1)×A。递推,有F(n)=F(1)×An1。矩阵相乘有结合律,因此An1可以用快速幂来求。

  如何确定矩阵A的值?假设有限制12不可以贴在一起,那么如果第n1个骰子最上面的数字是1,那么在它上面的第n个骰子的最上面的数字不可以是512不可以贴一起,而2的对立面是5),而f(i,5)是通过向量F(i1)与矩阵A的第5列相乘得到的,因此矩阵中的c15就为0。同理可得c24=0。更一般的形式,如果有限制xy,那么就有cxy^=cyx^=0,其中x^表示x的对立面。

  因为我们实现的函数是矩阵乘矩阵,因此为了统一方便,我们把向量F(i)也扩充乘一个6×6矩阵,除了第一行外,其余都为0

F(i)=[f(i,1)f(i,2)f(i,3)f(i,4)f(i,5)f(i,6)000000000000000000000000000000]

  AC代码如下,时间复杂度为O(63×log n)

复制代码
 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 typedef long long LL;
 7 
 8 const int N = 6, mod = 1e9 + 7;
 9 
10 int mp[N] = {3, 4, 5, 0, 1, 2}; // 各个面的对立面映射
11 
12 void mult(int c[][N], int a[][N], int b[][N]) { // C = A * B
13     int tmp[N][N] = {0};
14     for (int i = 0; i < N; i++) {
15         for (int j = 0; j < N; j++) {
16             for (int k = 0; k < N; k++) {
17                 tmp[i][j] = (tmp[i][j] + (LL)a[i][k] * b[k][j]) % mod;
18             }
19         }
20     }
21     
22     memcpy(c, tmp, sizeof(tmp));
23 }
24 
25 int main() {
26     int n, m;
27     scanf("%d %d", &n, &m);
28     
29     int a[N][N];
30     for (int i = 0; i < N; i++) {
31         for (int j = 0; j < N; j++) {
32             a[i][j] = 4;    // 一开始先把矩阵的值都初始化为4
33         }
34     }
35     
36     while (m--) {
37         int x, y;
38         scanf("%d %d", &x, &y);
39         x--, y--;   // 为了方便,把1~6映射到0~5
40         a[x][mp[y]] = a[y][mp[x]] = 0;  // 有限制的数字取0
41     }
42     
43     // F(1)一开始为一个全4的行向量(只有一个骰子,没有限制,每个数字都有4个方向),同时把向量扩展为矩阵
44     int f[N][N] = {4, 4, 4, 4, 4, 4};
45     for (int i = n - 1; i; i >>= 1) {   // 快速幂
46         if (i & 1) mult(f, f, a);   // F = F * A
47         mult(a, a, a);  // A = A * A
48     }
49     
50     int ret = 0;
51     for (int i = 0; i < N; i++) {
52         ret = (ret + f[0][i]) % mod;
53     }
54     printf("%d", ret);
55     
56     return 0;
57 }
复制代码

 

参考资料

  AcWing 1217. 垒骰子(蓝桥杯C++ AB组辅导课):https://www.acwing.com/video/793/

posted @   onlyblues  阅读(549)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2021-03-14 一元多项式的乘法与加法运算
Web Analytics
点击右上角即可分享
微信分享提示