程序设计思维week14 选做题

矩阵快速幂专场!

然而还要结合dp
关于矩阵快速幂,以前学的时候写过一篇博客
还是和普通快速幂一样的原理,通过二进制拆分指数来让幂的复杂度降到log级别,但是和普通快速幂的应用环境不同,矩阵快速幂常用来优化递推式的计算,比较经典的问题就是求斐波那契数列的第1e7+项,这个问题在以前的博客里也写过了。
这周遇到的问题都是对dp的优化

Q老师染砖

衣食无忧的 Q老师 有一天突发奇想,想要去感受一下劳动人民的艰苦生活。
具体工作是这样的,有 N 块砖排成一排染色,每一块砖需要涂上红、蓝、绿、黄这 4 种颜色中的其中 1 种。且当这 N 块砖中红色和绿色的块数均为偶数时,染色效果最佳。
为了使工作效率更高,Q老师 想要知道一共有多少种方案可以使染色效果最佳,你能帮帮他吗?
Input
第一行为 T,代表数据组数。(1 ≤ T ≤ 100)
接下来 T 行每行包括一个数字 N,代表有 N 块砖。(1 ≤ N ≤ 1e9)
Output
输出满足条件的方案数,答案模 10007。
Sample Input
2
1
2
Sample Output
2
6
解题思路
记录三种状态情况, a[i]表示i个格子红绿个数均为偶数的情况, b[i]为i个格子,红绿个数均为奇数的情况, c[i]为i个格子红绿有一个为偶数的情况
递推公式就可以写出来了
A[i] = 2 * A[i-1] + C[i-1]
B[i] = 2 * B[i-1] + C[i-1]
C[i] = 2 * A[i-1] + 2 * B[i-1] + 2 * C[i-1]
但是n太大,不用试就知道直接推会TLE
这时候用到矩阵快速幂的常规套路,推一个常数矩阵帮助我们算结果
在这里插入图片描述
代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <climits>
#include <algorithm>
#include <queue>
#include <vector>
#define ll long long
using namespace std;
const int mod = 10007;
int k,n;
const int maxn=3;
struct Matrix{
    int m[maxn][maxn];
    Matrix operator*(const Matrix&b)
    {
        Matrix ret;
        for(int i=0;i<maxn;i++)
        {
            for(int j=0;j<maxn;j++)
            {
                for(int k=0;k<maxn;k++)
                {
                    ret.m[i][j]+=(m[i][k]*b.m[k][j])%mod;
                    ret.m[i][j]%=mod;
                }
            }
        }
        return ret;
    }
    Matrix()
    {
        memset(m,0,sizeof(m));
    }
};
Matrix e;
void init()
{
    for(int i=0;i<maxn;i++)
    {
        e.m[i][i] = 1;
    }
}
Matrix fastpow(Matrix x,int y)
{
    Matrix ans = e;//别忘了初始化为单位矩阵,相当于普通快速幂从1开始乘
    while(y)
    {
        if(y&1)ans = ans * x;
        x = x * x;
        y >>= 1;
    }
    return ans;
}
int main()
{
    init();
    int t;
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d",&n);
        Matrix a;
        a.m[0][0]=2; a.m[0][1]=0; a.m[0][2]=1;
        a.m[1][0]=0; a.m[1][1]=2; a.m[1][2]=1;
        a.m[2][0]=2; a.m[2][1]=2; a.m[2][2]=2;
        Matrix ansm = fastpow(a,n-1);
        int ans = 0;
        ans = (ansm.m[0][0]*2 + ansm.m[0][2]*2)%mod;
        printf("%d\n",ans);
    }
    return 0;
}

Q老师度假

忙碌了一个学期的 Q老师 决定奖励自己 N 天假期。
假期中不同的穿衣方式会有不同的快乐值。
已知 Q老师 一共有 M 件衬衫,且如果昨天穿的是衬衫 A,今天穿的是衬衫 B,则 Q老师 今天可以获得 f[A][B] 快乐值。
在 N 天假期结束后,Q老师 最多可以获得多少快乐值?
Input
输入文件包含多组测试样例,每组测试样例格式描述如下:
第一行给出两个整数 N M,分别代表假期长度与 Q老师 的衬衫总数。(2 ≤ N ≤ 100000, 1 ≤ M ≤ 100)
接下来 M 行,每行给出 M 个整数,其中第 i 行的第 j 个整数,表示 f[i][j]。(1 ≤ f[i][j] ≤ 1000000)
测试样例组数不会超过 10。
Output
每组测试样例输出一行,表示 Q老师 可以获得的最大快乐值。
Sample Input
3 2
0 1
1 0
4 3
1 2 3
1 2 3
1 2 3
Sample Output
2
9
解题思路
天数连续,问题具有子结构性质;第n天的结果由今天和第n-1天的衣服决定,是个很常规的递推
令𝑓[𝑖][𝑗]表示第 i 天,穿的衣服为 j 所获得的快乐值总和
𝑓[𝑖][𝑗]=max(𝑓[𝑖−1][𝑘]+𝐻[𝑘][𝑗]),1≤𝑘≤𝑀
但是直接推的时间复杂度为O(N * M * M),无法接受
根据已经忘光的 离散数学知识,可以发现我们的解法能换成矩阵乘法的形式
𝐶[𝑖][𝑗]=∑𝐴[𝑖][𝑘]∗𝐵[𝑘][𝑗] ⇒max(𝐴[𝑖][𝑘]+𝐵[𝑘][𝑗])
因为+对max是可分配的,同样*对+是可分配的
矩阵快速幂要求矩阵乘法具有结合律,这里有一个证明过程:
在这里插入图片描述然后就套矩阵快速幂快乐AC了

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <climits>
#include <algorithm>
#include <queue>
#include <vector>
#define ll long long
using namespace std;
const int mod = 10007;
int n,m;
const int maxn=101;
struct Matrix{
    ll m[maxn][maxn];
    Matrix operator*(const Matrix&b)
    {
        Matrix ret;
        for(int i=0;i<maxn;i++)
        {
            for(int j=0;j<maxn;j++)
            {
                for(int k=0;k<maxn;k++)
                {
                    ret.m[i][j] = max(ret.m[i][j],m[i][k] + b.m[k][j]);//重新定义乘法
                }
            }
        }
        return ret;
    }
    Matrix()
    {
        memset(m,0,sizeof(m));
    }
};
Matrix e;
void init()
{
    for(int i=0;i<maxn;i++)
    {
        e.m[i][i] = 0;//因为运算规则改了,单位矩阵的意义也要改变
    }
}
Matrix fastpow(Matrix x,int y)
{
    Matrix ans = e;
    while(y)
    {
        if(y&1)ans = ans * x;
        x = x * x;
        y >>= 1;
    }
    return ans;
}
int main()
{
    init();
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        Matrix a;
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<m;j++)
            {
                scanf("%lld",&a.m[i][j]);
            }
        }
        Matrix ansm = fastpow(a,n-1);
        ll ans = 0;
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<m;j++)
            {
                ans = max(ans,ansm.m[i][j]);
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值