赌圣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
一开始完全不知道矩阵快速幂有什么用,直到昨天吃了亏,果然,还是刷题刷的少啊,我的线代真是学了浪费= =。
通过邻接矩阵来保存第一个骰子到第n个骰子两个状态的连通性即可能性, 通过矩阵乘法来实现状态堆积。最后的矩阵的每个元素即为第n个骰子各个状态的可能性也是所有可能性的各个分支。
之所以要乘n个4是因为每个骰子都可以旋转,且面是固定的,所以只有4个可能,而不是全排列的6种。
建立tool数组的意义在于, 骰子的头部不能是前一个状态不具有连通性的那个状态的背面,这话说的有点绕。
#include
#include
typedef long long LL;
using namespace std;
const LL mod = 1e9 + 7;
const int tool[] = { 0,4,5,6,1,2,3 }; //存取下标的对立面
struct Matrix {
LL m[7][7];
Matrix() { memset(m, 0, sizeof(m)); }
};
//普通快速幂
LL Qpow(LL num, LL n) {
LL ans = 1;
while (n) {
if (n & 1) ans = ans * num % mod;
n >>= 1;
num = num * num % mod;
}
return ans;
}
//矩阵乘法
Matrix MarMul(Matrix a, Matrix b) {
Matrix ans;
for (int i = 1; i <= 6; i++) {
for (int j = 1; j <= 6; j++) {
for (int k = 1; k <= 6; k++) {
ans.m[i][j] = (ans.m[i][j] + a.m[i][k] * b.m[k][j]) % mod;
}
}
}
return ans;
}
//矩阵快速幂
Matrix MarQpow(Matrix a, LL n) {
Matrix ans;
for (int i = 1; i <= 6; i++) ans.m[i][i] = 1;
while (n > 0) {
if (n & 1) ans = MarMul(ans, a);
n >>= 1;
a = MarMul(a, a);
}
return ans;
}
int main() {
LL n, m, a, b, output = 0;
Matrix ans;
for (int i = 1; i <= 6; i++) {
for (int j = 1; j <= 6; j++) ans.m[i][j] = 1;
}
cin >> n >> m;
while (m--) {
cin >> a >> b;
ans.m[a][tool[b]] = ans.m[b][tool[a]] = 0;
}
ans = MarQpow(ans, n - 1);
for (int i = 1; i <= 6; i++)
for (int j = 1; j <= 6; j++) output = (output + ans.m[i][j]) % mod;
cout << (output * Qpow(4, n)) % mod << endl;
return 0;
}