垒骰子 【矩阵快速幂】

在网上看了一些答案,大部分都没有AC,

题目不算难,但是不好理解,想通之后就很好做了,

想了大半天之后,终于做了出来,分享一下我的理解。。。。

大家可以在这个网站上提交,看下自己敲的对不对:https://www.dotcpp.com/oj/problem2261.html

题目:


垒骰子

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

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

「输入格式」
第一行两个整数 n m
n表示骰子数目
接下来 m 行,每行两个整数 a b ,表示 a 和 b 数字不能紧贴在一起。

「输出格式」
一行一个数,表示答案模 10^9 + 7 的结果。

「样例输入」

2 1
1 2

「样例输出」

544

「数据范围」
对于 30% 的数据:n <= 5
对于 60% 的数据:n <= 100
对于 100% 的数据:0 < n <= 10^9, m <= 36


资源约定:
峰值内存消耗 < 256M
CPU消耗  < 2000ms

思路:

 ① 建立一个冲突矩阵Conflict[i][j],表示 第i面 和  第j面的背面 冲突,至于为什么要这样,举个例子:

      比如题目中的例子, Conflict[1][5] 和 Conflict[2][4] 设成0,

     代表 当第一层的骰子,1朝上的时候,那么第二层骰子,5就不能朝上(2的背面),不然的话1和2就紧贴了。

② 建立一个Count[j]矩阵,记录当前某高度下的各个面朝上的总方案数,当然刚开始的时候Count[1]=Count[2]=···=Count[6]=1,因为第一层顶层可以是任意一面。

③ 现在Conflict 乘 Count ,可以得到第二层 各个面朝上 的总方案数。

    乘完以后,Count里面,第一个5的意思是,如果第一层的顶面是1,那第二层的顶面有5种摆放方式(因为5不能朝上)。

    然后每个面的总方案数加起来5+5+6+6+6+6 = 34,就是垒骰子总方案数了。

④ 如果要得到第三层 各个面朝上的总方案数,只需要Cnflict 乘以 第二层的count矩阵;

⑤ 那么现在得出结论,

要知道第2层 各个面朝上的总方案数,只需要计算 Cnflict  * Count 

要知道第3层 各个面朝上的总方案数,只需要计算 Cnflict  * Cnflict  * Count 

以此类推,

要知道第N层 各个面朝上的总方案数, 只需要计算 Cnflict * Cnflict * ··· * Cnflict  * Count   N-1 个Cnflict相乘

⑥最后的count每项加起来,然后乘以4^n,因为我们只是计算的竖直方向不冲突的方案,骰子的4个侧面可以随便朝向,总共有n个骰子,所以最后结果要乘以4的n次方。

#include <iostream>
#include <cstring>

#define ll long long
using namespace std;

const ll MOD = 1e9 + 7;
int back[] = {0, 4, 5, 6, 1, 2, 3};
struct Matrix
{
    ll v[7][7];
};
Matrix mul(Matrix A, Matrix B)
{
    Matrix C;
    for (int i = 1; i <= 6; i++)
        for (int j = 1; j <= 6; j++)
        {
            C.v[i][j] = 0;
            for (int k = 1; k <= 6; k++)
            {
                C.v[i][j] += ((A.v[i][k] % MOD) * (B.v[k][j] % MOD)) % MOD;
                C.v[i][j] %= MOD;
            }
        }
    return C;
}
Matrix pow(Matrix A, ll k)
{
    Matrix ans;
    for (int i = 1; i <= 6; i++)
        for (int j = 1; j <= 6; j++)
            if (i == j)
                ans.v[i][j] = 1;
            else
                ans.v[i][j] = 0;
    while (k)
    {
        if (k & 1)
            ans = mul(ans, A);
        A = mul(A, A);
        k >>= 1;
    }
    return ans;
}
ll pow2(ll num, ll k)
{
    int ans = 1;
    while (k)
    {
        if (k & 1)
            ans = ans * num % MOD;
        num = num * num % MOD;
        k >>= 1;
    }
    return ans % MOD;
}
int main()
{
    Matrix Conflict;
    for (int i = 1; i <= 6; i++)
        for (int j = 1; j <= 6; j++)
            Conflict.v[i][j] = 1;
    ll n;
    int m, a, b;
    cin >> n >> m;
    while (m--)
    {
        cin >> a >> b;
        Conflict.v[a][back[b]] = 0;
        Conflict.v[b][back[a]] = 0;
    }

    Conflict = pow(Conflict, n - 1);

    ll sum = 0;
    for (int i = 1; i <= 6; i++)
        for (int j = 1; j <= 6; j++)
        {
            sum += Conflict.v[i][j] % MOD;
            sum %= MOD;
        }

    sum = (sum * pow2(4, n)) % MOD;

    cout << sum << endl;

    system("pause");
    return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值