[蓝桥杯]垒骰子

垒骰子

题目描述

赌圣 atm 晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。

经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!

我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。

假设有 mm 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。

atm 想计算一下有多少种不同的可能的垒骰子方式。

两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。

由于方案数可能过多,请输出模 109+7109+7 的结果。

不要小看了 atm 的骰子数量哦~

输入描述

输入第一行两个整数 n,mn,m,nn 表示骰子数目;

接下来 mm 行,每行两个整数 a,ba,b ,表示 aa 和 bb 数字不能紧贴在一起。

其中,0<n≤109,m≤360<n≤109,m≤36。

输出描述

输出一行一个数,表示答案模 109+7109+7 的结果。

输入输出样例

示例

输入

2 1
1 2

输出

544

运行限制

  • 最大运行时间:2s
  • 最大运行内存: 256M

总通过次数: 848  |  总提交次数: 2042  |  通过率: 41.5%

难度: 困难   标签: 2015, 省赛, 动态规划, 搜索

算法思路:矩阵快速幂优化动态规划

核心问题分析

垒骰子问题需要计算在给定互斥条件下垒n个骰子的合法方式数量,并对 109+7 取模。关键挑战在于:

  1. ​骰子结构​​:1-4、2-5、3-6互为对面
  2. ​互斥条件​​:若数字a与b互斥,则a的面不能与b的面紧贴
  3. ​旋转特性​​:每个骰子可绕垂直轴旋转,固定上下两面后有4种摆放方式
  4. ​超大n值​​:n≤109,传统动态规划会超时
优化策略:矩阵快速幂
  1. ​状态定义​​:

    • 设 dp[i][j] 表示第i个骰子(从下往上)顶面为j的方案数(j∈[1,6])
    • 第i个骰子底面为 op[j](对面关系)
  2. ​转移方程​​:

  3. 乘以4是因为固定上下两面后,骰子可水平旋转4次

  4. ​矩阵化转换​​:

    • 将DP转移表示为6×6矩阵乘法
    • 构造转移矩阵 A:A[k][j]={10​若 k 与 op[j] 不互斥否则​
    • 状态向量关系:Fn​​=F1​​×An−1
    • 最终方案数:(∑Fn​​)×4nmod109+7
矩阵快速幂原理
  • 时间复杂度:O(63logn),完美处理 n=109

C++ 代码实现

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

typedef long long ll;
const int MOD = 1e9 + 7;
const int DICE = 6;

// 骰子对面关系:1-4, 2-5, 3-6
const int op[] = {0, 4, 5, 6, 1, 2, 3};

struct Matrix {
    ll m[DICE][DICE];
    Matrix() { memset(m, 0, sizeof(m)); }
    
    // 矩阵乘法
    Matrix operator*(const Matrix& other) const {
        Matrix res;
        for (int i = 0; i < DICE; i++)
            for (int k = 0; k < DICE; k++)
                for (int j = 0; j < DICE; j++)
                    res.m[i][j] = (res.m[i][j] + m[i][k] * other.m[k][j]) % MOD;
        return res;
    }
};

// 矩阵快速幂
Matrix matrix_pow(Matrix base, ll exp) {
    Matrix res;
    for (int i = 0; i < DICE; i++) res.m[i][i] = 1;  // 单位矩阵
    
    while (exp) {
        if (exp & 1) res = res * base;
        base = base * base;
        exp >>= 1;
    }
    return res;
}

// 整数快速幂
ll pow_mod(ll base, ll exp, ll mod) {
    ll res = 1;
    while (exp) {
        if (exp & 1) res = (res * base) % mod;
        base = (base * base) % mod;
        exp >>= 1;
    }
    return res;
}

int main() {
    ll n; int m;
    cin >> n >> m;
    
    // 初始化转移矩阵(全1表示初始无约束)
    Matrix A;
    for (int i = 0; i < DICE; i++)
        for (int j = 0; j < DICE; j++)
            A.m[i][j] = 1;
    
    // 处理互斥条件
    while (m--) {
        int a, b;
        cin >> a >> b;
        // 禁止a在下方时b在上方
        A.m[a-1][op[b]-1] = 0;
        // 禁止b在下方时a在上方
        A.m[b-1][op[a]-1] = 0;
    }
    
    // 计算A^(n-1)
    Matrix An = matrix_pow(A, n-1);
    
    // 求和初始状态到最终状态的路径
    ll total = 0;
    for (int i = 0; i < DICE; i++)
        for (int j = 0; j < DICE; j++)
            total = (total + An.m[i][j]) % MOD;
    
    // 乘以旋转方案数
    total = total * pow_mod(4, n, MOD) % MOD;
    
    cout << total << endl;
    return 0;
}

代码解析

  1. ​数据结构​​:

    • Matrix 类封装6×6矩阵运算
    • op[] 存储骰子对面关系(索引1~6)
  2. ​关键函数​​:

    • matrix_pow():矩阵快速幂,O(logn) 计算 An−1
    • pow_mod():整数快速幂,高效计算 4nmod109+7
  3. ​流程控制​​:

    • ​初始化​​:转移矩阵全1(无约束时所有转移合法)
    • ​互斥处理​​:对每个互斥对(a,b),将矩阵中对应位置置0
    • ​结果计算​​:矩阵求和后乘以旋转方案数

实例验证

输入:2 1 和 1 2

  1. ​矩阵构建​​:

    • 初始矩阵全1(36个有效转移)
    • 互斥(1,2) → 禁止转移:(1→5) 和 (2→4)

  1. ​计算过程​​:

    • A1 元素和 = 34(原36 - 2个禁止)
    • 42=16
    • 结果 = 34×16=544 ✓

注意事项

  1. ​下标转换​​:

    • 骰子编号1~6 → 矩阵索引0~5
    • op[b]-1 将骰子值转换为矩阵列索引
  2. ​取模时机​​:

    • 矩阵乘法每步取模防溢出
    • 最终结果取模
  3. ​边界处理​​:

    • n=1时直接输出 6×4=24
    • m=0时矩阵保持全1

测试点设计

测试类型输入预期输出验证要点
最小规模1 024单骰子旋转
无约束2 0576全连接验证
完全互斥2 360无合法方案
部分互斥2 1\n1 2544示例验证
最大规模1e9 0非0时间/内存压力测试
对立面互斥2 1\n1 40对面必然紧贴

优化建议

  1. ​矩阵乘法优化​​:

    // 循环展开优化(6x6固定大小)
    for (int i = 0; i < 6; i++) {
        for (int k = 0; k < 6; k++) {
            if (!A.m[i][k]) continue;  // 稀疏矩阵优化
            for (int j = 0; j < 6; j++) {
                res.m[i][j] += A.m[i][k] * B.m[k][j];
            }
        }
    }
  2. ​内存预分配​​:

    • 矩阵大小固定为6×6,避免动态内存分配
  3. ​并行计算​​:

    #pragma omp parallel for collapse(2)
    for (int i = 0; i < 6; i++)
        for (int j = 0; j < 6; j++)
            // 并行计算元素
  4. ​输入加速​​:

    ios::sync_with_stdio(false);
    cin.tie(0);  // 关闭同步加速输入
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鑫鑫向栄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值