这道题目来自蓝桥杯2015年省赛AB组,考察的是动态规划与矩阵快速幂的结合应用。题目描述为:用n个骰子垒成一列,要求相邻两个骰子的接触面数字不满足给定的排斥条件,求所有合法垒法的总数。
在实际场景中,这类问题常见于游戏开发中的物理引擎设计、概率统计中的状态转移计算,以及密码学中的排列组合验证。题目将骰子的6个面编号为1-6,给出m组排斥条件(如1和2不能接触),要求计算不违反任何排斥条件的垒法总数,结果对1e9+7取模。
最直观的解法是动态规划。设dp[i][j]表示第i个骰子顶部数字为j时的方案数。状态转移方程为:
dp[i][j] = Σ dp[i-1][k] * compatible(j, k)
其中compatible(j,k)表示数字j与k是否兼容(即不排斥)
这种解法时间复杂度为O(n*6^2),当n=1e9时显然不可行。此时需要引入矩阵快速幂优化。
将状态转移关系表示为转移矩阵T,其中T[i][j]表示顶部数字i能否转移到j(兼容时为1,否则为0)。初始状态向量V为全1向量(第一个骰子各面朝上均有1种方式)。
通过矩阵快速幂可将时间复杂度降为O(6^3 logn),关键步骤如下:
注意:实际实现时需要考虑骰子的对称性。当顶部数字确定时,侧面有4种旋转方式,因此每个合法转移需要乘以4的系数。
cpp复制// 初始化6x6全1矩阵
vector<vector<ll>> T(6, vector<ll>(6, 1));
// 处理排斥条件
while(m--) {
int a, b;
cin >> a >> b;
// 转换为0-based索引
T[a-1][opposite(b-1)] = 0;
T[b-1][opposite(a-1)] = 0;
}
// 对面数字关系
int opposite(int x) {
static const int opp[] = {3,4,5,0,1,2};
return opp[x];
}
cpp复制typedef vector<vector<ll>> Matrix;
Matrix multiply(const Matrix &A, const Matrix &B) {
Matrix res(6, vector<ll>(6));
for(int i=0; i<6; ++i)
for(int j=0; j<6; ++j)
for(int k=0; k<6; ++k)
res[i][j] = (res[i][j] + A[i][k]*B[k][j]) % MOD;
return res;
}
Matrix matrix_pow(Matrix mat, ll power) {
Matrix res(6, vector<ll>(6));
// 初始化为单位矩阵
for(int i=0; i<6; ++i) res[i][i] = 1;
while(power) {
if(power & 1) res = multiply(res, mat);
mat = multiply(mat, mat);
power >>= 1;
}
return res;
}
cpp复制Matrix T_pow = matrix_pow(T, n-1);
ll ans = 0;
for(int i=0; i<6; ++i)
for(int j=0; j<6; ++j)
ans = (ans + T_pow[i][j]) % MOD;
// 考虑每个骰子的4种旋转可能
ans = ans * pow_mod(4, n, MOD) % MOD;
python复制n=2, 排斥条件(1,2)
正确结果:6*4*4 - 4*4 = 80
cpp复制#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
const int MOD = 1e9+7;
int opposite(int x) { return (x+3)%6; }
Matrix multiply(const Matrix &A, const Matrix &B) {
Matrix res(6, vector<ll>(6));
for(int i=0; i<6; ++i)
for(int j=0; j<6; ++j)
for(int k=0; k<6; ++k)
res[i][j] = (res[i][j] + A[i][k]*B[k][j]) % MOD;
return res;
}
ll pow_mod(ll a, ll b) {
ll res = 1;
while(b) {
if(b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
int main() {
ll n; int m;
cin >> n >> m;
Matrix T(6, vector<ll>(6, 1));
while(m--) {
int a, b;
cin >> a >> b;
T[a-1][opposite(b-1)] = 0;
T[b-1][opposite(a-1)] = 0;
}
Matrix res(6, vector<ll>(6));
for(int i=0; i<6; ++i) res[i][i] = 1;
ll power = n-1;
Matrix tmp = T;
while(power) {
if(power & 1) res = multiply(res, tmp);
tmp = multiply(tmp, tmp);
power >>= 1;
}
ll ans = 0;
for(int i=0; i<6; ++i)
for(int j=0; j<6; ++j)
ans = (ans + res[i][j]) % MOD;
ans = ans * pow_mod(4, n) % MOD;
cout << ans << endl;
return 0;
}
这种矩阵快速幂优化DP的方法可以推广到许多线性递推问题中,如:
在实际工程中,当遇到具有线性转移规律的大规模计数问题时,都可以考虑构建转移矩阵并用快速幂优化。关键点在于:
我在实际项目中曾用类似方法优化过游戏中的技能冷却计算系统,将O(n)的递推转化为O(logn)的矩阵运算,性能提升显著。一个经验是:当n>1e6时,这种优化带来的收益会非常明显。