垒骰子(25 point(s))
赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。 atm想计算一下有多少种不同的可能的垒骰子方式。两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。由于方案数可能过多,请输出模 10^9 + 7 的结果。
不要小看了 atm 的骰子数量哦~
输入格式:
第一行两个整数 n mn表示骰子数目接下来 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
注意
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意: main函数需要返回0注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。注意: 所有依赖的函数必须明确地在源文件中 #include , 不能通过工程设置而省略常用头文件。
提交时,注意选择所期望的编译器类型。
这道题的思路来源来自博客:http://blog.youkuaiyun.com/lonverce/article/details/45169285#reply
讲的非常的详细。
这里如果下面想要看懂,请先看原博客讲解。
但是我在思考具体矩阵相乘的选择过程,和每个矩阵行列所代表的实际含义时,根据原博主对complict矩阵的定义,所得到的含义是相互矛盾的,虽然最终提交结果正确但我却解释不通,而我感觉将其含义进行以下更改更好理解
单行矩阵Count[1][ j ]来记录高度为N时, 顶面为j的总方案数.这个定义没有问题
原博主对complict[][]矩阵定义:
1 . Complict[ i ][ k ] 表示: 有一个点数为i的面, 它的反面与点数为k的面存在冲突.
上述定义意味着, 如果1和2存在冲突, 那么Compact[4][2] 和 Compact[5][1] 都为0 , 因为4的反面是1, 5的反面是2.
而我认为该矩阵正确的定义应该为:Complict[i][k]表示,点数为k的面的反面和点数为i的面是否存在冲突!!!!!
下面举个例子进行讲解 k
i 1 1 1 1 1 1
j 1 2 3 4 5 6 1 1 1 1 1 1 (该矩阵只是随便举得例子,可能不合理)
(___ , ___ , ___ , ___ , ___ , ___) x 1 1 1 1 1 1
1 0 1 1 1 1
0 1 1 1 1 1
1 1 1 1 1 1
Count * Complict = newCount;就是一个选择的过程
因为Count记录的是当前高度顶面为j的情况下的总方案数,那么我们的下一个高度即
newCount同样是顶面为j情况下的总方案数,那么我们选择时也就是判断是否符合题意时,就需要判断j的对面是否和我们枚举的面(上一个高度的顶面)有冲突,因此这就是Complict[ i ][ k ]矩阵记录的是点数为i的面, 它的反面与点数为k的面是否存在冲突,而不是点数为i的面和点数为k的面是否存在冲突。
根据上图选择矩阵的列k最终会变成当前顶面1-6,所以k就是新的顶面,j是旧的顶面,而根据矩阵的乘法法则的理解,我们可以把后面选择矩阵的i和j看成相同含义,即都是旧的顶面。所以相乘的选择过程实际就是,当新的顶面为k时,枚举上一个高度时的每一个顶面,如果k的对立面和上一个高度时的我们枚举的顶面冲突就不加那个情况下的个数否则加上,这样就说得通了。
举个例子:上图,前面矩阵(就一行)乘后面选择矩阵的第一列时,也就是我们在求再放一个骰子让他顶面是1,这是这列的0,1数字就代表1的对立面数字和1-6是否冲突,也就是枚举的和上一个骰子每种情况下是否冲突,上图红色的0说明1的对立面和5是冲突的,说明上一个骰子顶面是5的情况不能加上,其他的都可以加,这样我们就得到了新的顶面为1的所有情况,其他的相信大家也就好理解了。
code:
//LQ(7)垒骰子
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int MAXN = 8;
const ll MOD = 1e9 + 7;
int parner[7] = {0,4,5,6,1,2,3};
int n,m;
struct matrix{
ll con[MAXN][MAXN];
matrix(){
for(int i = 0; i < MAXN; i++){
for(int j = 0; j < MAXN; j++){
con[i][j] = 0;
}
}
}
};
matrix mul(matrix a,matrix b){//矩阵乘法
matrix ans;
for(int i = 1; i <= 6; i++){
for(int j = 1; j <= 6; j++){
if(a.con[i][j]){
for(int k = 1; k <= 6; k++){
ans.con[i][k] =(ans.con[i][k] + (a.con[i][j] * b.con[j][k] % MOD)) % MOD;
}
}
}
}
return ans;
}
matrix m_pow(matrix a,int b){//矩阵快速幂
matrix ans;
for(int i = 1; i <= 6; i++){
ans.con[i][i] = 1;
}
while(b){
if(b & 1){
ans = mul(ans,a);
}
a = mul(a,a);
b >>= 1;
}
return ans;
}
ll q_pow(ll a,ll b,ll c){//常数快速幂取模
ll ans = 1;
while(b){
if(b & 1){
ans = ans * a % c;
}
a = a * a % c;
b >>= 1;
}
return ans;
}
int main(){
matrix counts;
for(int i = 1; i <= 6; i++){
counts.con[1][i] = 1;
}
matrix complict;
for(int i = 1; i <= 6; i++){
for(int j = 1; j <= 6; j++){
complict.con[i][j] = 1;
}
}
scanf("%d%d",&n,&m);
for(int i = 1; i <= m; i++){
int a,b;
scanf("%d%d",&a,&b);
complict.con[a][parner[b]] = complict.con[b][parner[a]] = 0;//重点就是这里的储存问题,正确储存方式
//若按照原作者定义应该这样储存complict.con[parner[a]][b] = complict.con[parner[b]][a] = 0,虽然提交仍然正确,道理上确实讲不通的。
}
counts = mul(counts,m_pow(complict,n-1));
ll ans = 0;
for(int i = 1; i <= 6; i++){
ans += counts.con[1][i];
}
ll times = q_pow(4,n,MOD);//最后每一个顶面可以水平四个方向转,所以还要乘4^n
ans = ans * times % MOD;
printf("%lld\n",ans);
return 0;
}