矩阵乘法

首先,矩阵乘法的定义:设A是n*m的矩阵,B是m*p的矩阵,C=A*B是一个n*p的矩阵,并且对于任意∈【1,n】,j∈【1,p】,C[i][j]=∑A(i,k)*B(k,j)。也就是说,参加矩阵乘法的第一个矩阵的列数必须等于第二个矩阵的行数。结果矩阵C的一个点i,j是矩阵A的第i行和矩阵B的第j列上的数分别相乘所得积的和。

由此我们可以看出,矩阵乘法支持乘法结合律,即A*B*C=A*(B*C).满足分配率,即(A+B)*C=A*C+B*C.

我们考虑一种情况,当F是1*n矩阵,A是n*n矩阵的时候,F'=F*A仍然是一个1*n矩阵。F与F'可以看做一个一维的数组,省略他们的行下标1.按照矩阵乘法的定义,任意j∈【1,n】,F'【j】=∑F【k】*A(k,j)。它的含义是,经过一次与A的矩阵乘法运算后,F数组中的第k个值,会以A(k,j)为系数累加到F'数组中的第j个值上,等价于在一个向量的k,j两个状态之间发生了递推。

所以,矩阵乘法最大的用处在于加速递推。当然这也是有条件的:对于问题来说

1、可以抽象出一个长度为n的一维数组,该向量在每个单位时间内发生一次变化。

2、变化的形式是一个线性递推(只有若干加法或乘一个系数的运算)。

3、该递推式在每个时间可能作用于不同的数据,但是其本身不变。

4、向量变化时间很长,但是向量长度n不大。

那么我么就可以使用矩阵乘法来进行优化。我们把这个长度为n的一维向量称为“状态矩阵”,把用于与“状态矩阵”相乘的固定不变的矩阵称为“转移矩阵”。若状态矩阵中的第x个数对下一单位时间的状态矩阵中的第y个值产生影响,则把转移矩阵中的第x行第y列赋值为适当的系数。

例题:

BZOJ2973:石头游戏

Description

石头游戏的规则是这样的。

石头游戏在一个n行m列的方格阵上进行。每个格子对应了一个编号在0~9之间的操作序列。

操作序列是一个长度不超过6且循环执行、每秒执行一个字符的字符串。它包括:

数字0~9:拿0~9个石头到该格子。

NWSE:把这个格子内所有的石头推到相邻的格子。

D:拿走这个格子的石头。

石头游戏的问题是:当这个石头游戏进行了t秒之后,所有方格中最多的格子有多少个石头。

注意:所有格子的操作同时执行。

Input

第一行三个整数n, m, t, act。

接下来n行,每行m个字符,表示每个格子对应的操作序列。

最后act行,每行一个字符串,表示从0开始的每个操作序列。

Output

一个整数:游戏进行了t秒之后,所有方格中最多的格子有多少个石头。

Sample Input

1 6 10 3
0 1 1 1 1 2
1E
E
0

Sample Output

3
【样例解释】
这是另一个类似于传送带的结构。左边的设备0间隔地产生石头并向东传送。设备1向右传送,直到设备2。10秒后,总共产生了5个石头,2个在传送带上,3个在最右边。
【数据约定】
0≤n, m≤8。
1≤act≤10。
1≤t≤10^9。

 

分析:首先,我们发现,每单位时间内每一个格子都有各自的变化规则,序列长度很小且循环,所有变化是有规律的。序列长度小于等于6,所以最大公约数为60,也就是说每过60秒,变化规则变为与之前一样。所以当我们把60秒当做一次变化的时候,这种变化是保持不变的,他只是作用于不同的数据,因此我们先把这前60次变化的转移矩阵相乘,得到一个新的转移矩阵,然后对F数组进行矩阵乘法。当然,如果t=q*60+r(r>0&&r<60),我们之后还要将F数组与剩下的r组转移矩阵相乘,不过这剩下的r组相乘消耗的时间与之前的t组相比,就显得微不足道了。

那么我们怎么设计出转移矩阵呢?

首先,我们发现,如果此时轮到的序列中的字符为数字,则表示在这个格子加上数字的值,我们之前提到,如果想让当前状态矩阵的第x个值影响下一单位时间的状态矩阵的第y个值,我们需要在转移矩阵的第x行y列上赋值一个系数;我们此时要做的是,在第y个格子上加上temp,那么也就是说∑F(k)*A(k,y)=temp。最好的做法就是我们在F数组中找到一个k位置,将他赋值为1,A中的第k行y列赋值为temp。我们为了不影响其他的计算,并且有效的处理所有的加法,我们把特殊的F(0)作为源点,存储加法的系数1,然后A中则是令A(0,y)=temp。当然我们发现,下一个单位时间的状态矩阵的第y个值,是在之前的值得基础上加了temp,所以我们同时需要把A(y,y)赋值为1。

当遇到NSWD这四个字母时,就是把当前位置的数字全部给旁边的一个格子。那么比如说把当前格子(i,j)的数字给上面,那么就是下一个状态的格子(i-1,j)需要加上F(num(i,j))的值,所以我们把A中的第num(i,j)行第num(i-1,j)列赋值为1,就可以完成数字的转移,此时不需要再把A(num(i,j),num(i,j))赋值为1,因为它为0了。

代码实在是太丑陋,见谅~

#include <cstdio>
#include <cstring>
#include <iostream>
#define P(A,B) ((A-1)*m+B)
using namespace std;
typedef long long ll;
typedef struct matrix
{
    ll v[100][100];
}M;
M ans,x,emp,f[65];
int n,m,tim,act;
int len[20];
ll maxx;
char A[20][20],B[20][20],ch;
M mmul(M a,M b)
{
    M c=emp;
    int i,j,k;
    for(i=0;i<=n*m;i++)
        for(j=0;j<=n*m;j++)
            for(k=0;k<=n*m;k++)
                c.v[i][j]+=a.v[i][k]*b.v[k][j];
    return c;
}
void pm(int y)
{
    while(y)
    {
        if(y&1) ans=mmul(ans,x);
        x=mmul(x,x),y>>=1;
    }
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&tim,&act);
    int i,j,k,l,a,b;
    for(i=1;i<=n;i++)    scanf("%s",A[i]);
    for(i=0;i<act;i++)   scanf("%s",B[i]),len[i]=strlen(B[i]);
    for(i=0;i<=n*m;i++)  x.v[i][i]=1;
    ans.v[0][0]=1;
    for(l=0;l<60;l++)
    {
        f[l].v[0][0]=1;
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++)
            {
                k=A[i][j-1]-'0',ch=B[k][l%len[k]];
                if(ch>='0'&&ch<='9')  f[l].v[P(i,j)][P(i,j)]=1,f[l].v[0][P(i,j)]=ch-'0';
                if(ch=='N'&&i>1) f[l].v[P(i,j)][P(i-1,j)]=1;
                if(ch=='W'&&j>1) f[l].v[P(i,j)][P(i,j-1)]=1;
                if(ch=='S'&&i<n) f[l].v[P(i,j)][P(i+1,j)]=1;
                if(ch=='E'&&j<m) f[l].v[P(i,j)][P(i,j+1)]=1;
            }
        }
        x=mmul(x,f[l]);
    }
    pm(tim/60);
    for(i=0;i<tim%60;i++)    ans=mmul(ans,f[i]);
    for(i=1;i<=n*m;i++)  maxx=max(maxx,ans.v[0][i]);
    printf("%lld",maxx);
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值