[蓝桥杯 2015 省 AB] 垒骰子
题目描述
赌圣 atm 晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。
经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!
我们先来规范一下骰子: 1 1 1 的对面是 4 4 4, 2 2 2 的对面是 5 5 5, 3 3 3 的对面是 6 6 6。
假设有 m m m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。
atm 想计算一下有多少种不同的可能的垒骰子方式。
两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。
由于方案数可能过多,请输出模 1 0 9 + 7 10^9+7 109+7 的结果。
不要小看了 atm 的骰子数量哦~。
输入格式
第一行两个整数 n n n, m m m。
n n n 表示骰子数目。
接下来 m m m 行,每行两个整数 a a a, b b b,表示 a a a 和 b b b 数字不能紧贴在一起。
输出格式
一行一个数,表示答案模 1 0 9 + 7 10^9+7 109+7 的结果。
样例 #1
样例输入 #1
2 1
1 2
样例输出 #1
544
提示
对于 30 % 30\% 30% 的数据: n ≤ 5 n \le 5 n≤5。
对于 60 % 60\% 60% 的数据: n ≤ 100 n \le 100 n≤100。
对于 100 % 100\% 100% 的数据: 0 < n ≤ 1 0 9 , m ≤ 36 0<n \le 10^9,m \le 36 0<n≤109,m≤36。
时限 2 秒, 256M
蓝桥杯 2015 年省赛 AB 组 I 题。
思路
本道题先看数据范围,n比较大,那么我们可以先思考当n比较小的时候,本题怎么做:我们可以思考用dp来解决这道题(因为本道题我们可以试着用集合划分(也就是当前状态可以用前一个状态转移过来的)),因此,我们用f[i][j]表示第i个骰子的上面的数字j,再分析,我们可以知道,如下图
f[i][j]
转移状态的系数都是固定的,(这里为什么系数为4?因为每个骰子只能转动4次),对于这样的情况,我们可以利用矩阵的乘法进行加速(类似于斐波那契数列的优化版本),因此我们可以:
注意:f初始化为6个4,还有就是我们可以把f的矩阵大小跟a的矩阵对齐,这样就不必再写个mul
函数了。
代码
//n<=1e9考虑快速幂
//只有当n的范围小的时候用dp更好
//大的话,我们可以考虑矩阵优化版
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N = 6,mod=1e9+7;
bool st[N][N];
int n,m;
int get_op(int x){
if(x>=3)return x-3;
return x+3;
}
void mul(int c[][N],int a[][N],int b[][N]){
static int temp[N][N];
memset(temp,0,sizeof temp);
for(int i=0;i<6;i++){
for(int j=0;j<6;j++){
for(int k=0;k<6;k++){
temp[i][j]=(temp[i][j]+a[i][k]*b[k][j])%mod;
}
}
}
memcpy(c,temp,sizeof temp);
}
signed main(){
cin>>n>>m;
while(m--){
int a,b;
cin>>a>>b;
a--,b--;
st[a][get_op(b)]=true;
st[b][get_op(a)]=true;
}
int f[N][N]={4,4,4,4,4,4};
int a[N][N]={0};
for(int i=0;i<6;i++){
for(int j=0;j<6;j++){
if(!st[i][j]){
a[i][j]=4;
}
}
}
n--;
while(n){
if(n&1)mul(f,f,a);
mul(a,a,a);
n>>=1;
}
int res=0;
for(int i=0;i<6;i++){
res=(res+f[0][i])%mod;
}
cout<<res;
return 0;
}