题目描述
赌圣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
java代码递归1
首先给出两种递归解法效率较低,然后再给出一个动态规划法和一个矩阵快速幂算法。
此递归是从第一层推到第n层。
import java.util.*;
public class Main {
static int n;
static int m;
static boolean[][] conflict = new boolean[7][7]; // 若confilct[i][j]为true,则i与j冲突
static int[] op = { 0, 4, 5, 6, 1, 2, 3 }; // 记录每一个号码的对立面
static long ans = 0;
static int MOD = 1000000007;
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for (int i = 0; i < m; i++) {
int x = sc.nextInt();
int y = sc.nextInt();
conflict[x][y] = true;
conflict[y][x] = true;
}
for (int i = 1; i <= 6; i++) {
dfs(1, i);
}
// 第i个骰子的j点数朝上时可以左右旋转,共有四种情况
// 这里要让ans乘n次4
for (int i = 0; i < n; i++) {
ans = (ans * 4) % MOD;
}
System.out.println(ans);
sc.close();
}
/**
* 此递归是从下往上磊骰子
* @param k
* 当前在处理第几个骰子
* @param x
* 下面那个骰子朝上的那一面的点数
*/
private static void dfs(int k, int x) {
// 递归出口
if (k == n) {
ans = (ans + 1) % MOD;
return;
}
// 对于处理的第k个骰子,分别处理点数i朝上的情况
for (int i = 1; i <= 6; i++) {
int xia = op[i];
// 如果点数i朝上与其下的骰子冲突,则进入下一个for循环,考虑下一个点数
if (conflict[xia][x]) {
continue;
}
// 处理接下来的骰子
dfs(k + 1, i);
}
}
}
java代码递归2
此递归是从第n层向下递归到第1层。
这个递归我也有点晕,建议直接看后面的两种解法。
import java.util.*;
public class Main {
static int n;
static int m;
static boolean[][] conflict = new boolean[7][7]; // 若confilct[i][j]为true,则i与j冲突
static int[] op = { 0, 4, 5, 6, 1, 2, 3 }; // 记录每一个号码的对立面
static int MOD = 1000000007;
static long ans = 0;
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for (int i = 0; i < m; i++) {
int x = sc.nextInt();
int y = sc.nextInt();
conflict[x][y] = true;
conflict[y][x] = true;
}
// 从上往下递归
// up为当前处理骰子朝上一面的点数
for (int up = 1; up <= 6; up++) {
ans = (ans + dfs(up, n - 1)) % MOD;
}
System.out.println(ans);
sc.close();
}
/**
*
* @param up
* 上面一个骰子朝上的点数
* @param cnt
* 剩余处理骰子的个数
* @return
*/
private static long dfs(int up, int cnt) {
long ans = 0;
if (cnt == 0) {
return 4;
}
for (int i = 1; i <= 6; i++) {
// 如果上一个骰子朝下的点数与当前骰子朝上的点数冲突
if (conflict[op[up]][i]) {
continue;
}
ans = (ans + 4 * dfs(i, cnt - 1)) % MOD;
}
return ans;
}
}
java代码动态规划
得到动态规划函数便可轻易得到该算法。
需要注意的是滚动数组的应用和快速幂运算算法。
import java.util.*;
public class Main {
static int n;
static int m;
static boolean[][] conflict = new boolean[7][7]; // 若confilct[i][j]为true,则i与j冲突
static int[] op = { 0, 4, 5, 6, 1, 2, 3 }; // 记录每一个号码的对立面
static int MOD = 1000000007;
static long ans = 0;
static long[][] dp = new long[2][7]; // 滚动数组
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for (int i = 0; i < m; i++) {
int x = sc.nextInt();
int y = sc.nextInt();
conflict[x][y] = true;
conflict[y][x] = true;
}
int cur = 0; // 实际处理dp数组的行数
// dp初始化
for (int j = 1; j <= 6; j++) {
dp[cur][j] = 1;
}
// 当前处理层数
for (int i = 2; i <= n; i++) {
cur = 1 - cur; // 改变滚动数组行数
// 当前在处理第i层点数j朝上的情况
for (int j = 1; j <= 6; j++) {
// 对于第i层点数j朝上的情况,如果第i-1层的k朝上的点与第i层j朝上的骰子不冲突
// 有状态转移方程dp[i][j] = dp[i][j] + dp[i-1][k]
long sum = 0;
for (int k = 1; k <= 6; k++) {
if (conflict[k][op[j]] == false) {
sum = (sum + dp[1 - cur][k]) % MOD;
}
}
dp[cur][j] = sum;
}
}
// 将第n层所有点数朝上的情况相加
for (int j = 1; j < 7; j++) {
ans = (ans + dp[cur][j]) % MOD;
}
// 每一层的每一个点数有4种旋转方案
// 快速幂求4的n次方
int p = n;
long temp = 4;
long product = 1;
while (p != 0) {
if ((p & 1) == 1) {
product = product * temp % MOD;
}
temp = temp * temp % MOD;
p >>= 1;
}
ans = (ans * product) % MOD;
System.out.println(ans);
sc.close();
}
}
java代码矩阵快速幂
如上代码时间复杂度为O(n),如下代码可将时间复杂度优化为O(log n)。
import java.util.*;
public class Main {
static int n;
static int m;
static int[] op = { 0, 4, 5, 6, 1, 2, 3 }; // 记录每一个号码的对立面
static final int MOD = 1000000007;
static long ans = 0;
static long[][] conflict = new long[6][6]; // 冲突矩阵
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for (int i = 0; i < conflict.length; i++) {
for (int j = 0; j < conflict[i].length; j++) {
conflict[i][j] = 1;
}
}
for (int i = 0; i < m; i++) {
int x = sc.nextInt();
int y = sc.nextInt();
// 注意冲突矩阵的设置
conflict[op[x] - 1][y - 1] = 0;
conflict[op[y] - 1][x - 1] = 0;
}
// 求冲突矩阵的n-1次幂
long[][] mPow_n_1 = mPow(conflict, n - 1);
for (int i = 0; i < mPow_n_1.length; i++) {
for (int j = 0; j < mPow_n_1[i].length; j++) {
ans = (ans + mPow_n_1[i][j]) % MOD;
}
}
// 快速计算4的n次方
long product = pow(4, n);
// 在处理第i个骰子的j点朝上时有四种旋转方法
// 将矩阵快速幂运算得到的结果乘以4的n次方
ans = (ans * product) % MOD;
System.out.println(ans);
sc.close();
}
/**
* 计算x的n次方
*
* @param x
* @param n
* @return
*/
private static long pow(int x, int n) {
long res = 1;
while (n != 0) {
if ((n & 1) == 1) {
res = res * x % MOD;
}
x = x * x % MOD;
n >>= 1;
}
return res;
}
/**
* 计算conflict矩阵的n次方
*
* @param conflict
* @param n
* @return
*/
private static long[][] mPow(long[][] conflict, int n) {
// 定义一个单位矩阵
long[][] res = new long[conflict.length][conflict[0].length];
for (int i = 0; i < res.length; i++) {
for (int j = 0; j < res[i].length; j++) {
if (i == j)
res[i][j] = 1;
else
res[i][j] = 0;
}
}
// 快速幂
while (n != 0) {
if ((n & 1) == 1) {
res = mMul(res, conflict);
}
conflict = mMul(conflict, conflict);
n >>= 1;
}
return res;
}
/**
* 计算矩阵m1和m2的乘积
*
* @param m1
* @param m2
* @return
*/
private static long[][] mMul(long[][] m1, long[][] m2) {
long[][] res = new long[m1.length][m2[0].length];
for (int i = 0; i < res.length; i++) {
for (int j = 0; j < res[i].length; j++) {
for (int k = 0; k < m2.length; k++) {
res[i][j] = (res[i][j] + m1[i][k] * m2[k][j]) % MOD;
}
}
}
return res;
}
}