矩阵快速幂+动态规划=蓝桥杯 垒骰子
如果还不知道什么是矩阵快速幂,可以参加我的另一篇文章:矩阵快速幂详解
题目
分析
看到 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 i−1 个骰子任意面朝上都可以,则方案数为
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[i−1][1]+dp[i−1][2]+...dp[i−1][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[i−1][1]+dp[i−1][3]+...+dp[i−1][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[i−1][2]+dp[i−1][3]+...+dp[i−1][6])∗4 ( 5 5 5 的对面才为 2 2 2)
则可知对于第 i i i 个骰子, j j j 面朝上时的方案数为,第 i − 1 i-1 i−1 个骰子与 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))
对于示例我们可以得到如下矩阵表达式
注意标为红色的 0 0 0 ,是因为 4 4 4 和 5 5 5 的对面分别为 1 1 1 和 2 2 2 ,而 1 1 1 和 2 2 2 相互排斥。
递推得
由此我们只需求出矩阵 A A A 的 n − 1 n-1 n−1 次方,而利用矩阵快速幂我们即可在 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);
}
}