蓝桥杯垒骰子

题目描述

赌圣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;
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值