P8624 [蓝桥杯 2015 省 AB] 垒骰子

题目描述

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

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

我们先来规范一下骰子: 的对面是 , 的对面是 , 的对面是 。

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

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

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

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

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

输入格式

第一行两个整数 ,。

表示骰子数目。

接下来 行,每行两个整数 ,,表示 和 数字不能紧贴在一起。

输出格式

一行一个数,表示答案模 的结果。

输入输出样例 #1

输入 #1

2 1
1 2

输出 #1

544

说明/提示

对于 的数据:。

对于 的数据:。

对于 的数据:。

时限 2 秒, 256M

蓝桥杯 2015 年省赛 AB 组 I 题。

解析

这是一个经典的矩阵快速幂题目。

问题核心

我们要把 个骰子垒起来,每一层的骰子有 6 种朝上的面。确定了朝上的面,朝下的面也就确定了。 每一层骰子的放置受制于下面一层骰子的朝上面(必须与当前层的朝下面不互斥)。 同时,每一层骰子除了上下面的朝向确定外,侧面还可以有 4 种旋转方向。

状态转移

假设 表示垒了 个骰子,且最上面那个骰子(第 个)朝上的面是 的方案数。 那么第 个骰子的朝下面就是 。 它能垒在第 个骰子上,前提是第 个骰子的朝上面 与 不互斥。 而且,对于每一种合法的连接,第 个骰子都可以有 4 种侧向旋转方式。

转移方程: 其中要求 和 不互斥。

矩阵加速

由于 高达 ,简单的 递推会超时。观察转移方程,这是一个线性递推关系,可以用矩阵快速幂加速。

构造一个 的转移矩阵 。 表示从“上一层顶面为 ”转移到“当前层顶面为 ”的方案倍数。 如果上一层顶面 和当前层底面 不互斥,则 (因为有4种旋转)。 否则 。

初始向量 为垒 1 个骰子的情况。1 个骰子时,任意面 朝上都有 4 种方案(侧面旋转),所以初始状态每个面都是 4。但为了方便矩阵运算,我们通常直接计算 ,然后统计所有元素的和再乘 4。

最终答案计算

设最终矩阵 。 由于第 1 个骰子不管是哪个面朝上,基础方案都是 4(侧面旋转),且不需要和地面判互斥。 所以总方案数 = 。

代码

//
// Created by 23264 on 2026/2/12.
//
#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
constexpr int MOD = 1e9 + 7;
 
// 骰子对立面映射
// 面 1 对 4,2 对 5,3 对 6
// 下标 0 1 2 3 4 5 6
// 对立 0 4 5 6 1 2 3
int op[7] = {0,4,5,6,1,2,3};
 
// 互斥表
bool conflict[7][7];
 
struct Matrix
{
    ll mat[7][7]{};
    Matrix()
    {
        memset(mat,0,sizeof(mat));
    }
};
 
// 矩阵乘法
Matrix multiply(const Matrix& a,const Matrix& b)
{
    Matrix c;
    for (int i = 1;i <= 6;i++)
    {
        for (int j = 1;j <= 6;j++)
        {
            for (int k = 1;k <= 6;k++)
            {
                c.mat[i][j] = (c.mat[i][j] + a.mat[i][k] * b.mat[k][j]) % MOD;
            }
        }
    }
    return c;
}
 
// 矩阵快速幂
Matrix power(Matrix a,int n)
{
    Matrix res;
    // 初始化单位矩阵
    for (int i = 1;i <= 6;i++)
    {
        res.mat[i][i] = 1;
    }
 
    while (n > 0)
    {
        if (n & 1)
        {
            res = multiply(res,a);
        }
        a = multiply(a,a);
        n >>= 1;
    }
    return res;
}
 
int main()
{
    // 优化输入输出
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
 
    int n,m;
    cin >> n >> m;
 
    // 读入互斥关系
    for (int i = 0;i < m;i++)
    {
        int u,v;
        cin >> u >> v;
        conflict[u][v] = true;
        conflict[v][u] = true;
    }
 
    // 构建转移矩阵
    Matrix base;
    for (int i = 1;i <= 6;i++)
    {       // i 是当前骰子朝上的面
        for (int j = 1;j <= 6;j++)
        {   // j 是下面骰子朝上的面
            // 当前骰子的朝下面是 op[i]
            // 如果下面骰子的朝上面 j 与 当前骰子的朝下面 op[i] 不互斥
            if (!conflict[j][op[i]])
            {
                base.mat[i][j] = 4; // 有4种旋转可能
            }
            else
            {
                base.mat[i][j] = 0;
            }
        }
    }
 
    // 计算 Matrix^(n-1)
    // 如果 n=1,power(base, 0) 返回单位矩阵,后续求和结果为 6,最后乘 4 得到 24,正确
    Matrix res = power(base,n - 1);
    ll ans = 0;
    // 统计矩阵所有元素的和
    // 逻辑解释:
    // 初始状态(第1层)是一个全为4的向量 V1 = [4, 4, 4, 4, 4, 4]^T
    // Vn = Base^(n-1) * V1
    // 我们要求的是 Vn 所有元素的和
    // 这等价于 Base^(n-1) 的所有元素之和 乘以 4
    ll sum_matrix = 0;
    for (int i = 1;i <= 6;i++)
    {
        for (int j = 1;j <= 6;j++)
        {
            sum_matrix = (sum_matrix + res.mat[i][j]) % MOD;
        }
    }
    ans = (sum_matrix * 4) % MOD;
    cout << ans << endl;
}