[蓝桥杯2015初赛]垒骰子(从dp到矩阵快速幂的优化)

题目链接:http://oj.ecustacm.cn/problem.php?id=1256
[蓝桥杯2015初赛]垒骰子(从dp到矩阵快速幂的优化)_第1张图片
分析
dp+滚动数组
(代码无法通过全部数据)
dp[i][j]表示高度为 i , 顶面点数为 j 的方案数
dp[ i ][ j ] 就等于 i-1 高度时所有与j的反面无冲突的方案数累加
总方案数还要乘以(4^n), 因为每一个骰子可以4面转
每一层的规划只与前一层有关, 所以可以采用滚动数组
代码

#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int mod=1e9+7;
int n,m;
int book[7][7];/*book[i][j]=0:点数为i的面与点数为j的面存在冲突*/
void init()
{
    for(int i=1; i<=6; i++)
        for(int j=1; j<=6; j++)
            book[i][j]=1;
}
LL quick_mi(LL x,int n)
{
    LL tot=1;
    while(n)
    {
        if(n&1)
            tot=tot*x%mod;
        x=x*x%mod;
        n>>=1;
    }
    return tot;
}
int main()
{
    cin>>n>>m;
    init();
    int s1,s2;
    while(m--)
    {
        cin>>s1>>s2;/*s1和s2存在冲突*/
        book[s1][s2]=0;
        book[s2][s1]=0;
    }
    LL dp[2][7];
    int e=0;/*滚动标志*/
    for(int i=1; i<=6; i++)
        dp[e][i]=1;/*起初,每个面朝上的摆法为1*/
    for(int i=2; i<=n; i++)
    {
        e=1-e;
        for(int j=1; j<=6; j++)
        {
            dp[e][j]=0;/*下面的dp[e][j]+=dp[1-e][k],体现滚动数组的滚动性,这里重新清零*/
            for(int k=1; k<=6; k++)
            {
                if(book[j][k])
                    dp[e][j]+=dp[1-e][k];/*一层的一种情况加上它低一层的所有情况*/
            }
            dp[e][j]%=mod;
        }
    }
    LL sum=0;
    for(int i=1; i<=6; i++)
        sum=(sum+dp[e][i])%mod;
    LL ans=quick_mi(4,n);
    sum=sum*ans%mod;
    printf("%lld\n",sum);
    return 0;
}

矩阵快速幂优化
思想
引入矩阵求斐波那契数列:
[蓝桥杯2015初赛]垒骰子(从dp到矩阵快速幂的优化)_第2张图片
通过对矩阵乘法的巧妙应用, 我们可以写成如此精致的递归式 .
仔细分析, 其实中间的矩阵的列向量是十分特别的, 它以近乎完美的方式对左边矩阵中的点值进行了有机性的取舍, 从而演变出了右边的矩阵.
右边的矩阵与左边的矩阵又具有相似的结构, 所以, 递归式可以无限地演变下去.
最终变为:
[蓝桥杯2015初赛]垒骰子(从dp到矩阵快速幂的优化)_第3张图片
改变dp数组的意义:
dp[ i ][ j ]=0表示: 有一个点数为i的面, 它的反面与点数为j的面存在冲突.
例如: 如果1和2存在冲突, 那么dp[4][2] 和dp[5][1] 都为0 , 因为4的反面是1, 5的反面是2.
单行矩阵book[1][ j ]:记录高度为N时, 顶面为j的总方案数.
矩阵book记录了当前某高度下的各个面的总方案数, 而矩阵dp的列向量描述了"选择", 这已经跟斐波那契例子中的结构相当类似了, 从而我们可以证明:
book * dp = newbook
如果book记录了高度为N的各个面的总方案数;
那么 newbook 记录了高度为N+1的各个面的总方案数
当高度为N时, 式子是这样的 :
book = book * dp * dp *…*dp ; ( dp被乘了N-1次)
代码

#include
#include
#include
using namespace std;
typedef long long LL;
const LL mod=1e9+7;
int n,m;
int to[7]= {0,4,5,6,1,2,3};
struct node
{
    LL v[7][7];
} first;
void init()
{
    for(int i=1; i<=6; i++)
        for(int j=1; j<=6; j++)
            first.v[i][j]=1;
}
node mul(node x,node y)/*两个6*6的矩阵*/
{
    node t;
    for(int i=1; i<=6; i++)
    {
        for(int j=1; j<=6; j++)
        {
            t.v[i][j]=0;
            for(int k=1; k<=6; k++)
            {
                t.v[i][j]+=(x.v[i][k]*y.v[k][j]%mod);
                t.v[i][j]%=mod;
            }
        }
    }
    return t;
}
node mul1(node x,node y)/*  1*66*6的矩阵  */
{
    node t;
    for(int i=1; i<=1; i++)
    {
        for(int j=1; j<=6; j++)
        {
            t.v[i][j]=0;
            for(int k=1; k<=6; k++)
            {
                t.v[i][j]+=(x.v[i][k]*y.v[k][j]%mod);
                t.v[i][j]%=mod;
            }
        }
    }
    return t;
}
node quick_mi(node first,int k)
{
    node temp;
    for(int i=1; i<=6; i++)/*初始化为单位矩阵*/
        for(int j=1; j<=6; j++)
        {
            if(i==j)
                temp.v[i][j]=1;
            else
                temp.v[i][j]=0;
        }
    while(k)
    {
        if(k&1)
            temp=mul(temp,first);
        first=mul(first,first);
        k>>=1;
    }
    return temp;
}
LL quick_mi1(LL x,int n)
{
    LL tot=1;
    while(n)
    {
        if(n&1)
            tot=tot*x%mod;
        x=x*x%mod;
        n>>=1;
    }
    return tot;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        while(m--)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            first.v[to[a]][b]=0;
            first.v[to[b]][a]=0;
        }
        node book;
        for(int i=1; i<=6; i++)
            book.v[1][i]=1;/*16基础矩阵的book表示以i为顶面的情况数*/
        node ans=quick_mi(first,n-1);/*m对冲突情况考虑后的6*6矩阵*/
        ans=mul1(book,ans);
        LL sum=0;
        for(int i=1; i<=6; i++)/*此处还是dp累加的思想*/
            sum=(sum+ans.v[1][i])%mod;
        LL xx=quick_mi1(4,n);
        sum=sum*xx%mod;
        printf("%lld\n",sum);
    }
    return 0;
}

不要温顺的走进那个良夜,激情不能被消沉的暮色淹

你可能感兴趣的:(巧用各种数组,数论)