问题描述:
赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。
经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!
我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。
假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。
atm想计算一下有多少种不同的可能的垒骰子方式。两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。由于方案数可能过多,请输出模 10^9 + 7 的结果。
「输入格式」
第一行两个整数 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
思路一:首先想到的是动态规划。我们一共要垒n层,那就一层层的垒。比如第一层一共有六种情况,分别是1,2,3,4,5,6面朝上,每一种都只有一种情况;第二层1朝上有多少种呢?我们可以将1朝上的骰子依次分别垒在第一层六个骰子上,如果4(1的对面是4)不与下面的骰子朝上的面冲突,就可以垒起来。
因此,动态规划的表达式就出来了。我们用dp[i][j]表示表示第i层j面朝上一共有多少种可能。根据前面的分析,我们可以得到以下表达式:
c
o
n
f
l
i
c
t
[
j
]
[
k
]
=
0
conflict[j][k]=0
conflict[j][k]=0的意思是j与k不冲突。递推式很好理解,第i层j面朝上最多只有六种(如果不冲突),但是我们要排除掉冲突的情况。初始时dp[0][1 到 6]都是1。需要注意的是,我们最后输出的不是dp[n-1][1]累加到dp[n-1][6],而是它们的和乘上
4
n
4^n
4n,因为每一层每个筛子都可以转动。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
const long long maxn = 1e7 + 5;
const long long mod = 1e9 + 7;
int op[7];
int conflict[7][7] = {0};
int dp[maxn][7];
void init() {
op[1] = 4;
op[4] = 1;
op[2] = 5;
op[5] = 2;
op[3] = 6;
op[6] = 3;
for(int i = 1;i <= 6;i++) {
dp[0][i] = 1;
}
}
long long fastpow(int x, long long y) {
long long res = 1, base = x % mod;
while(y) {
if(y & 1) {
res = (res * base) % mod;
}
base = (base * base) % mod;
y /= 2;
}
return res;
}
int main() {
cin>>n>>m;
init();
int a, b;
for(int i = 0;i < m;i++) {
cin>>a>>b;
conflict[a][b] = conflict[b][a] = 1;
}
for(int i = 1;i < n;i++) {
for(int j = 1;j <= 6;j++) {
for(int k = 1;k <= 6;k++) {
if(!conflict[op[j]][k]) {
dp[i][j] += dp[i-1][k] % mod;
}
}
}
}
int res = 0;
for(int i = 1;i <= 6;i++) {
res += dp[n-1][i] % mod;
}
res = res * fastpow(4, n);
cout<<res;
return 0;
}
结果分析:该代码存在两个比较严重的问题:一是申请数组dp[maxn][7]时会报错,因为实在太大了;二是三层循环那里会超时,因此该方法不可行,需要换一种思路。
思路二:我们换一种思路,我们构建一个冲突矩阵c,如果3与6冲突,则c[op[a]][b] = 0且c[op(b)][a]=0。c[op[a]][b] = 0的意思是a的对面与b是冲突的,也就是不能垒在一起。因为在上面动态规划的思路中,我们要算每一层各面朝上各有多少种可能,最后输出的是dp[n-1],dp[n-1]表示最后一层所有可能之和。我们容易知道dp[0]也就是第一层每个数都是1,也就是说dp[0]是一个全为1的列向量,我们用冲突矩阵c乘上dp[0],根据矩阵乘法规则,得到的也是一个6行1列的列向量,并且乘法结果其实就是dp[1]的值。那么则有:
因此,我们只要算出冲突矩阵的n-次幂,然后再乘上一个全为1的列向量dp[0],最后将结果向量里面每个数相加即可。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
int n, m;
int op[7];
void init() {
op[1] = 4;
op[4] = 1;
op[2] = 5;
op[5] = 2;
op[3] = 6;
op[6] = 3;
}
struct matrix {
ll mat[6][6];
init(int flag) {
if(flag == 1) {
//memset(mat, 1, sizeof(mat));错误写法
for(int i = 0;i < 6;i++) {
for(int j = 0;j < 6;j++) {
mat[i][j] = 1;
}
}
}else if(flag == 2) { //单位矩阵
memset(mat, 0, sizeof(mat));
for(int i = 0;i < 6;i++) {
mat[i][i] = 1;
}
}else {
memset(mat, 0, sizeof(mat));
}
}
};
void print(matrix x) {
for(int i = 0;i < 6;i++) {
for(int j = 0;j < 6;j++) {
cout<<x.mat[i][j]<<' ';
}
cout << endl;
}
}
matrix mul(matrix a,matrix b) { //return a*b
matrix c; //0矩阵
c.init(3);
for(int i = 0;i < 6;i++) {
for(int j = 0;j < 6;j++) {
for(int k = 0;k < 6;k++) {
c.mat[i][j] += ((a.mat[i][k] % mod) * (b.mat[k][j] % mod)) % mod;
c.mat[i][j] %= mod;
}
}
}
return c;
}
matrix fast_pow(matrix A, int n) { //return A^n % mod
matrix B; //单位矩阵
B.init(2);
while(n) {
if(n & 1) {
B = mul(B, A);
}
A = mul(A, A);
n >>= 1;
}
return B;
}
long long fastpow(int x, long long y) { //快速幂
long long res = 1, base = x % mod;
while(y) {
if(y & 1) {
res = (res * base) % mod;
}
base = (base * base) % mod;
y /= 2;
}
return res;
}
int main() {
cin >> n >> m;
init();
int a, b;
matrix conflict;
conflict.init(1);
for(int i = 0;i< m;i++) {
cin >> a >> b;
conflict.mat[op[a] - 1][b - 1] = 0; //冲突
conflict.mat[op[b] - 1][a - 1] = 0;
}
matrix final = fast_pow(conflict, n-1);
int res = 0;
for(int i = 0;i < 6;i++) {
for(int j = 0;j < 6;j++) {
res += final.mat[i][j];
}
}
res *= fastpow(4, n);
printf("%d", res);
return 0;
}