题目描述
赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。
经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!
我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。
假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。
atm想计算一下有多少种不同的可能的垒骰子方式。
两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。
由于方案数可能过多,请输出模 10^9 + 7 的结果。
不要小看了 atm 的骰子数量哦~
示例
输入第一行两个整数 n m;
n表示骰子数目;
接下来 m 行,每行两个整数 a b ,表示 a 和 b 数字不能紧贴在一起。
对于 30% 的数据:n <= 5
对于 60% 的数据:n <= 100
对于 100% 的数据:0 < n <= 10^9, m <= 36
例如:
用户输入:
2 1
1 2
输出一行一个数,表示答案模 10^9 + 7 的结果。
例如:
对应输出:
544
暴力解法(dfs):
import java.util.Scanner;
public class LanQiao36 {
static int res = 0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();// n个骰子
int m = sc.nextInt();// m对互斥关系
int[][] nums = new int[m][2];// 存储每一组互斥关系
for(int i = 0; i < m; i++){
for(int j = 0; j < 2; j++){
nums[i][j] = sc.nextInt();
}
}
int[] dianShu = {
1, 2, 3, 4, 5, 6};
dfs(n, nums, dianShu, 0, 1);
long ans = (long)(res * Math.pow(4, n)) % 1000000007;
System.out.println(ans);
}
public static void dfs(int n, int[][] nums, int[] dianShu, int k, int temp){
//temp 存储上一个骰子的顶面点数是多少
if(k == n){
res++;
return;
}
for(int i = 0; i < dianShu.length; i++){
//当 k 大于等于 1 的时候,即到了第二个骰子的时候才用和上一个骰子比较是否出现互斥,只有第一个骰子是没办法比的
//第一次做的时候没想出来如何实现以上思路,但后来发现只要加上 k >= 1就行
if(k >= 1){
if(isValid(nums, temp, getDuiLi(dianShu[i]))){
dfs(n, nums, dianShu, k + 1, dianShu[i]);
}
}else{
dfs(n, nums, dianShu, k + 1, dianShu[i]);
}
}
}
public static boolean isValid(int[][] nums , int x, int y){
//判断是否互斥
for(int[] arr : nums){
if((arr[0] == x && arr[1] == y) || (arr[0] == y && arr[1] == x)){
return false;
}
}
return true;
}
public static int getDuiLi(int num){
//取得一个骰子上面点数的对立面的点数
if(num <= 3){
return num + 3;
}
return num - 3;
}
}
将判断是否互斥的语句改进一下:
import java.util.Scanner;
public class LanQiao37 {
static int res = 0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();// n个骰子
int m = sc.nextInt();// m对互斥关系
boolean[][] nums = new boolean[6][6];// 存储每一组互斥关系,i代表第一个骰子,j代表第二个骰子
for (int i = 0; i < m; i++) {
int num1 = sc.nextInt();
int num2 = sc.nextInt();
//注意:都要赋成true
nums[num1 - 1][num2 - 1] = true;
nums[num2 - 1][num1 - 1] = true;
}
int[] dianShu = {
1, 2, 3, 4, 5, 6};
dfs(n, nums, dianShu, 0, 1);
long ans = (long) (res * Math.pow(4, n)) % 1000000007;
System.out.println(ans);
}
/**
* @param n n个骰子
* @param nums 存储第一个骰子和第二个骰子互斥的点数
* @param dianShu 骰子的点数
* @param k 记录是第几个骰子
* @param temp 存储上一个骰子的顶面点数是多少
*/
public static void dfs(int n, boolean[][] nums, int[] dianShu, int k, int temp) {
if (k == n) {
res++;
return;
}
for (int i = 0; i < dianShu.length; i++) {
//当 k 大于等于 1 的时候,即到了第二个骰子的时候才用和上一个骰子比较是否出现互斥,只有第一个骰子是没办法比的
//第一次做的时候没想出来如何实现以上思路,但后来发现只要加上 k >= 1就行
if (k >= 1) {
if (!nums[temp - 1][getDuiLi(dianShu[i]) - 1]) {
dfs(n, nums, dianShu, k + 1, dianShu[i]);
}
} else {
dfs(n, nums, dianShu, k + 1, dianShu[i]);
}
}
}
public static int getDuiLi(int num) {
//取得一个骰子上面点数的对立面的点数
if (num <= 3) {
return num + 3;
}
return num - 3;
}
}
动态规划法:
import java.util.Scanner;
public class LanQiao35 {
private static int[][] a = new int[7][7];//存放6个面的排斥关系,只用到数组下标1~6
private static long count;
private static final long C = 1000000007;
public static void main(String[] args) {
int num = 4;
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
for (int i = 0; i < m; i++) {
int a1 = sc.nextInt();
int a2 = sc.nextInt();
a[a1][a2] = 1;
a[a2][a1] = 1;
}
//滚动数组(只用了两层)
int dp[][] = new int[2][7];//dp[i][j]表示某一高度的骰子j面朝上的方案数
int e = 0;
for (int i = 1; i < 7; i++) {
dp[e][i] = 1;//初始化 已知高度为1的骰子的方案只有一种,此处忽略结果×4的情况,在下面加上
}
for (int i = 2; i <= n; i++) {
//从第二颗骰子算,到第n颗
e = 1 - e;
num = (int) ((num * 4) % C);
for (int j = 1; j < 7; j++) {
//遍历当前骰子各面
dp[e][j] = 0;//初始化下一颗骰子j面朝上的方案数为0
for (int k = 1; k < 7; k++) {
//遍历之前一颗骰子的6个面
if (!isValid(k, getDuiLi(j))) {
//不相互排斥,k为之前下方骰子的朝上面,getDuiLi(j)为当前骰子朝上面的对立面,即朝下面
dp[e][j] += dp[1 - e][k];
}
}
dp[e][j] = (int) (dp[e][j] % C);
}
}
for (int i = 1; i < 7; i++) {
count += dp[e][i];
count = count % C;
}
count = (count * num) % C;//这个地方相乘后仍然很大,是这个算法的弊端
//count = (count * Math.pow(4, n)) % C;
System.out.println(count);
}
public static int getDuiLi(int num) {
//取得一个骰子上面点数的对立面的点数
if (num <= 3) {
return num + 3;
}
return num - 3;
}
private static boolean isValid(int current, int last) {
if (a[current][last] == 1) {
//说明两个骰子互相排斥
return true;
}
return false;
}
}
非滚动数组:
import java.util.Scanner;
public class LanQiao34_05 {
private static int[][] a = new int[7][7];//存放6个面的排斥关系,只用到数组下标1~6
private static long count;
private static final long C = 1000000007;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
for (int i = 0; i < m; i++) {
int a1 = sc.nextInt();
int a2 = sc.nextInt();
a[a1][a2] = 1;
a[a2][a1] = 1;
}
int num = 4;
int[][] dp = new int[n + 1][7];
for(int j = 1; j < 7; j++){
dp[1][j] = 1;
}
for(int i = 2; i <= n; i++){
num = (num * 4) % (int)C;
for(int j = 1; j < 7; j++){
//遍历当前骰子的 1 到 6 的点数
for(int k = 1; k < 7; k++){
//遍历上一个骰子的 1 到 6 的点数
if(!isValid(getDuiLi(j), k)){
dp[i][j] += dp[i - 1][k];
}
}
dp[i][j] = dp[i][j] % (int)C;
}
}
for(int j = 1; j < 7; j++){
count += dp[n][j];
count = count % C;
}
count = (count * num) % C;//这个地方相乘后仍然很大,是这个算法的弊端
//count = (count * Math.pow(4, n)) % C;
System.out.println(count);
}
public static int getDuiLi(int num) {
//取得一个骰子上面点数的对立面的点数
if (num <= 3) {
return num + 3;
}
return num - 3;
}
private static boolean isValid(int current, int last) {
if (a[current][last] == 1) {
//说明两个骰子互相排斥
return true;
}
return false;
}
}