蓝桥杯垒骰子(动态规划)

1. 问题描述:

赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。 经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥! 我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。 假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。  atm想计算一下有多少种不同的可能的垒骰子方式。 两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。 由于方案数可能过多,请输出模 10^9 + 7 的结果。 

输入:

输入存在多组测试数据,对于每组数据: 
第一行两个整数 n m(0<n<10^9,m<=36) 
n表示骰子数目 
接下来 m 行,每行两个整数 a b ,表示 a 和 b 数字不能紧贴在一起。 

输出:

对于每组测试数据,输出一行,只包含一个数,表示答案模 10^9 + 7 的结果。 

样例输入:

2 1
1 2

样例输出

544

来源:http://oj.ecustacm.cn/problem.php?id=1256

2. 思路分析:

① 分析题目可以知道这道题目属于排列组合的题目,在垒每一层骰子的时候我们可以固定一个面的数字比如固定第1层面朝上的数字为[1, 6]范围内的一个数字(而第一层满足条件的就有6种情况),然后在垒第二层的时候也是固定一个面朝上的数字,并且考虑与第一层可能冲突的情况....依次到最后一层(每一次在垒骰子的时候都考虑冲突的这些面,排除掉一些方案),如果数据量比较小的时候可以通过数学的排列组合的方式计算出来。根据上面的分析我们可以知道第i层垒骰子的方案是由第上一层第i - 1层递推得到的,根据这个特点我们知道可以使用动态规划的思路进行解决,动态规划可以通过上一个状态递推到当前的状态,直到到达最后一个状态,所以我们是可以使用动态规划的思路解决的,在力扣中有一道也是关于骰子的题目,也是使用动态规划的思路解决的,感觉关于这些排列组合的问题都是可以考虑动态规划的思路去解决,而且与骰子相关的动态规划思路的套路都是类似的,通常要声明多维列表(python)或者数组(c/c++/java)进行解决。

② 因为涉及到n个骰子,而且在垒每一层骰子的时候可以固定一个面朝上的点数(而且可以理解的是上一层有n种情况根据上一次面朝上的数字情况就可以推导出当前这一层的情况:自己脑子里想想由上一层递推到这一层的骰子的排列的情况),所以我们可以声明一个二维列表,二维列表的大小为(n + 1)* 7(第一维和第二维多声明一个空间这样可以方便直接取出列表中的值),dp[i][j]表示的意思是垒第i层骰子面朝上的数字为j的组合方案数目,我们可以使用一维列表来表示骰子的对立面关系,利用一维列表下标之间的关系来确定各自的对立面,使用一个二维列表来表示哪些骰子的面是不能够紧贴在一起的,由上一个状态递推到当前状态中的状态转移方程也是比较容易容易的:与上一层的骰子不冲突的面说明是当前这一层是可以垒在上一层上面的,当前这一层骰子面朝上的数字为j那么只要是与j的对立面没有冲突的骰子都是可以紧贴在一起的,所以我们是可以使用三层循环来实现的,第一层循环表示垒当前的第i层骰子(初始化的第一层面朝上的数字为i的dp列表值都为1,表示当前面朝上的数字为i的都是只有一种方案),第二层循环表示固定当前这一层骰子面朝上的数字为j,j的范围为[1,6],第三层循环用来去除掉那些与当前面朝上数字为j的上一个骰子存在冲突的情况的组合方案,dp[i][j]累加上与之不冲突的情况。循环结束之后将第n层的面朝上的数字为1~6的情况加起来,这个结果表示每一层固定一个面朝上的数字为1-6的总和。因为每一层的骰子都是可以旋转的,并且是可以旋转4次的,所以需要将最终的结果再乘以4 ^ n(每一层将相加的结果乘以4每一层都可以将4提取出来最终就需要乘以4 ^ n),可以使用快速幂运算来减少计算4 ^ n的时间

③ 当我们遇到这些排列组合问题的时候多往动态方面的思路上想,考虑怎么样由上一个状态转移到到当前的状态,动态规划的核心就是状态之间的转移和转台方程式的编写,能够明确状态之间的转移接下来的就比较简单了。在提交到C语言网站上某些数据还是超时了(78分),在NewOnlineJudge网站上提交的答案显示错误,也不知道是为什么。

3. 代码如下:

# 快速幂运算
def quickPower(x: int, n: int):
    res = 1
    while n > 0:
        if n % 2 == 1:
            res *= x
        x *= x
        n //= 2
    return res


if __name__ == '__main__':
    # n表示表示骰子的数目
    n, m = map(int, input().split())
    # 声明一个二维列表用来记录哪些骰子的两个面是不能够挨在一起的
    notTogether = [[0] * 7 for i in range(7)]
    # 骰子的对立面
    oppos = [0, 4, 5, 6, 1, 2, 3]
    for i in range(m):
        a, b = map(int, input().split())
        # 不能够连接在一起的面
        notTogether[a][b] = 1
        notTogether[b][a] = 1
    dp = [[0] * 7 for i in range(n + 1)]
    for i in range(1, 7):
        # 第一层面朝上的点数为i的方案总数肯定为1
        dp[1][i] = 1
    mod = 10 ** 9 + 7
    # 从第二层开始推导
    for i in range(2, n + 1):
        # 第i层骰子面朝上点数为j
        for j in range(1, 7):
            # 如果当前的面朝上为j与上一层存在冲突的时候是不能够加上上一层对应的dp列表值
            for k in range(1, 7):
                if notTogether[oppos[j]][k] == 1:
                    continue
                else:
                    dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mod
            # print(dp[i][j])
    res = 0
    for i in range(7):
        res = (res + dp[n][i]) % mod
    # 最后要乘以4的n次幂
    print(res * quickPower(4, n) % mod)

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值