由vijos1067 联想到的矩阵乘法

最近做动态规划时,遇到了一道题,需要矩阵乘法,这里总结一下(感谢 @The_useless)
首先,这里写图片描述
即对于C矩阵来说,c[i][j]=A矩阵[i]行与B矩阵[j]列的乘积之和,Code:

matrix operator *(matrix a,matrix b){
    matrix c;
    c.init(a.n,b.m);
    fo(i,1,c.n)
        fo(j,1,c.m)
            fo(k,1,a.m)
                c.num[i][j]=(c.num[i][j]+a.num[i][k]*b.num[k][j])%MOD;

    return c;   
}

知道了这些就可以做题了.

题目大意:
一条长度为n的道路,每一次可以向前走1或k,询问走到n一共有多少方案.
数据范围:
第一行是闪烁技能的等级k(1<=k<=10)
第二行是监狱的个数n(1<=n<=2^31-1)

分析:
我们可以很快速的求出状态转移方程:

fo(i,1,n)
  fo(j,1,k-1)
      f[i]=(f[i]+f[j]);

我们发现由于n的值实在太大了,毫无疑问会超时,而k<=10,考虑k.根据矩阵乘法的性质,我们很容易可以得出,这是一道矩阵乘法可以解出的题目;
所以我们需要构造一个矩阵,使它与答案相乘等于下一次.
这里有两种构造方法,
若ans[1][k]

    unit.init(k,k);
    fo(i,1,k-1) unit.num[i][i+1]=1;
    fo(i,1,k) unit.num[k][i]=1; 

若ans[k][1] 反过来就好了
最后,记得单位矩阵(蒟蒻楼主刚刚知道),即一个矩阵p,与另一个任意矩阵q,p*q=q;
对角线全为1就好

最后附上code:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<queue>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int MOD=7777777,MAXN=15;
typedef long long ll;

struct matrix{
    ll n,m,num[MAXN][MAXN];
    void init(int x,int y){
        n=x;m=y;
        memset(num,0,sizeof(num));
    }
};
matrix operator *(matrix a,matrix b){
    matrix c;
    c.init(a.n,b.m);
    fo(i,1,c.n)
        fo(j,1,c.m)
            fo(k,1,a.m)
                c.num[i][j]=(c.num[i][j]+a.num[i][k]*b.num[k][j])%MOD;

    return c;   
}
int n,k;

matrix Solve(){
    matrix unit,ans;
    unit.init(k,k);
    fo(i,1,k-1) unit.num[i+1][i]=1;
    fo(i,1,k) unit.num[i][k]=1;
    ans.init(k,k); //ans是单位矩阵 
    fo(i,1,k) ans.num[i][i]=1;

    for(int tot=n-k+1;tot;tot>>=1)
    {
        if(tot&1) ans=ans*unit;
        unit=unit*unit;
    }
    return ans;
}
matrix ans; 

int main(){
    scanf("%d%d",&k,&n);
    ans.init(1,k);ans.num[1][1]=1;
    fo(i,2,k)
        fo(j,1,i-1)
            ans.num[1][i]=(ans.num[1][i]+ans.num[1][j])%MOD;
    ans=ans*Solve();
//  fo(i,1,k) printf("%lld ",ans.num[1][i]);
    printf("%lld",ans.num[1][k]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值