矩阵快速幂+动态规划=蓝桥杯 垒骰子

矩阵快速幂+动态规划=蓝桥杯 垒骰子

如果还不知道什么是矩阵快速幂,可以参加我的另一篇文章:矩阵快速幂详解

题目

截屏2021-04-12 19.50.40

分析

看到 n n n 的范围达到了 1 0 9 10^{9} 109 ,如果使用暴力搜索是不现实的。

我们看看是否可以利用动态规划解决问题呢?

首先定义我们 d p dp dp 数组的含义

d p [ i ] [ j ] dp[i][j] dp[i][j] 为第 i i i 个骰子, j j j 面朝上时的组合数。

列出 base case

只有一个骰子时, j j j 面朝上时,有四种方式。因为骰子有四个侧面,可以选择任意侧面。

即: d p [ 1 ] [ 1 ] = d p [ 1 ] [ 2 ] = . . . = d p [ 1 ] [ 6 ] = 4 dp[1][1] = dp[1][2] = ... =dp[1][6]=4 dp[1][1]=dp[1][2]=...=dp[1][6]=4

状态转移方程

如果没有排斥面,则对于第 i i i 个骰子, j j j 面朝上时,第 i − 1 i-1 i1 个骰子任意面朝上都可以,则方案数为

d p [ i ] [ j ] = ( d p [ i − 1 ] [ 1 ] + d p [ i − 1 ] [ 2 ] + . . . d p [ i − 1 ] [ 6 ] ) ∗ 4 dp[i][j] = (dp[i-1][1] + dp[i-1][2] + ...dp[i-1][6])*4 dp[i][j]=(dp[i1][1]+dp[i1][2]+...dp[i1][6])4

由示例 1 1 1 面和 2 2 2 面相排斥,则

d p [ i ] [ 4 ] = ( d p [ i − 1 ] [ 1 ] + d p [ i − 1 ] [ 3 ] + . . . + d p [ i − 1 ] [ 6 ] ) ∗ 4 dp[i][4]=(dp[i-1][1] + dp[i-1][3] +...+dp[i-1][6]) * 4 dp[i][4]=(dp[i1][1]+dp[i1][3]+...+dp[i1][6])4 4 4 4 的对面才为 1 1 1

d p [ i ] [ 5 ] = ( d p [ i − 1 ] [ 2 ] + d p [ i − 1 ] [ 3 ] + . . . + d p [ i − 1 ] [ 6 ] ) ∗ 4 dp[i][5]=(dp[i-1][2]+dp[i-1][3]+...+dp[i-1][6]) * 4 dp[i][5]=(dp[i1][2]+dp[i1][3]+...+dp[i1][6])4 5 5 5 的对面才为 2 2 2

则可知对于第 i i i 个骰子, j j j 面朝上时的方案数为,第 i − 1 i-1 i1 个骰子与 j j j 对面不排斥的面的方案数之和乘 4 4 4

再分析

我们已经推导出动态规划的解决方案,可是对于 1 0 9 10^9 109 的数据量,此方案还是无能为力,尽管似乎足够好了。

这时我们就需要引入矩阵快速幂将 O ( n ) O(n) O(n) 的时间复杂度降为 O ( log ⁡ ( n ) ) O(\log(n)) O(log(n))

对于示例我们可以得到如下矩阵表达式

截屏2021-04-12 20.15.39

注意标为红色的 0 0 0 ,是因为 4 4 4 5 5 5 的对面分别为 1 1 1 2 2 2 ,而 1 1 1 2 2 2 相互排斥。

递推得

截屏2021-04-12 20.32.02

由此我们只需求出矩阵 A A A n − 1 n-1 n1 次方,而利用矩阵快速幂我们即可在 O ( log ⁡ ( n ) ) O(\log(n)) O(log(n)) 的时间得到结果。

代码

import java.util.Scanner;

public class Main {

	static int n, m, mod = (int) (1e9 + 7);
    // oppo[j] 为 j 面的对面
	static int[] oppo = {0, 4, 5, 6, 1, 2, 3};
	static long[][] a = new long[7][7];
    // 单位矩阵 e
	static long[][] e = new long[7][7];
    // 初始化
	static {
		for (int i = 1; i < 7; i++) {
			e[i][i] = 1;
			for (int j = 1; j < 7; j++)
				a[i][j] = 4;
		}
	}
	
	static long[][] mul(long[][] x, long[][] y) {
		long[][] res = new long[7][7];
		for (int i = 1; i < x.length; i++) {
			for (int j = 1; j < y[0].length; j++) {
				long t = 0;
				for (int k = 1; k < x[0].length; k++) {
					t = (t + x[i][k] * y[k][j]) % mod;
				}
				res[i][j] = t;
			}
		}
		return res;
	}
	
	static long[][] pow(long[][] x, long n) {
		if (n == 0) return e;
		long[][] res = pow(mul(x, x), n / 2);
		if ((n & 1) != 0) res = mul(x, res);
		return res;
	}
	
	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();
			// 将排斥的面赋值为 0
			a[oppo[x]][y] = a[oppo[y]][x] = 0;
		}
		long[][] b = {{0, 0}, {0, 4}, {0, 4}, {0, 4}, {0, 4}, {0, 4}, {0, 4}};
		long[][] t = mul(pow(a, n - 1), b);
		long ans = 0;
		for (int i = 1; i < 7; i++)
			ans = (ans + t[i][1]) % mod;
		System.out.println(ans);
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值