8.9 暑假集训——矩阵快速幂

暂时还有题目没做出来,把已经写出来的题目先整理出来吧,顺便理一下矩阵快速幂的常见用法和思路,为做下面的题目做一个缓冲

由等式求递推矩阵,加速

F-M斐波那契数列

题意: 类似斐波那契数列给出一种数列的新定义:
F[0] = a
F[1] = b
F[n] = F[n-1] * F[n-2] ( n > 1 )
给出a, b, n,( 0 <= a, b, n <= 10^9),要求输出F[n]的值%(1e9+7)

思路: 写出几个F[n],a,b, ab, ab2, a2b3, a3b5, a5b8……
可以 观察出 这个序列的次数是斐波那契数列,由于n比较大,用矩阵快速幂加速一下~ 然后因为斐波那契数做次数可能比较大,所以用到费马小定理,取余(1e9+6)
这里写图片描述

之后问题的 关键只要写出斐波那契数列的递推矩阵 就可以啦~ 所以关键如图
这里写图片描述

话说这个名字前的M是代表matrix嘛? 正式比赛的时候会给这种提示咩???

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
struct Squ{
    LL s[2][2];  //一开始WA了还找不出是哪里,后来看到这里设成了int觉得都不想对自己说什么了……
};
Squ e,t;
const LL mod=1e9+7;
LL a,b,n;

LL quickpow(LL a,LL k){
    a%=mod;
    LL ans=1;
    while(k){
        if(k&1)
            ans=(ans*a)%mod;
        a=(a*a)%mod;
        k/=2;
    }
    return ans;
}

Squ mul(Squ a,Squ b){
    Squ ans;
    for(int i=0;i<2;i++)
    for(int j=0;j<2;j++){
        ans.s[i][j]=0;
        for(int k=0;k<2;k++){
            ans.s[i][j]+=a.s[i][k]*b.s[k][j];
            ans.s[i][j]%=(mod-1);
        }
    }
    return ans;
}

Squ squickpow(Squ a,LL k){
    Squ ans=e;
    while(k){
        if(k&1) ans=mul(ans,a);
        a=mul(a,a);
        k/=2;
    }
    return ans;
}

int main(){
    for(int i=0;i<2;i++)    //初始化单位矩阵
        for(int j=0;j<2;j++)
            if(i==j) e.s[i][j]=1;
            else e.s[i][j]=0;
    t.s[0][0]=t.s[0][1]=t.s[1][0]=1;
    t.s[1][1]=0;
    while(~scanf("%lld%lld%lld",&a,&b,&n)){
        a%=mod,b%=mod;
        if(n==0){  //两种情况单独处理
            printf("%lld\n",a%mod); continue;
        }
        else if(n==1){
            printf("%lld\n",b%mod); continue;
        }
        Squ ans=squickpow(t,n-2);  //对应图中的n-2次
        LL fn=(ans.s[0][0]+ans.s[0][1])%(mod-1);
        LL fn1=(ans.s[1][0]+ans.s[1][1])%(mod-1);
        long long res=(quickpow(a,fn1)*quickpow(b,fn))%mod;
        printf("%lld\n",res);
    }
    return 0;
}

Recursive sequence

题意: 这里写图片描述
告诉F[1],F[2]和n,(F[1],F[2],n<2^31), 求F[n]

思路: 知等式求递推矩阵,看到递推关系式中的i4,第一要想办法把它化成(i-1)4,所以可得
这里写图片描述

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
const LL mod=2147493647;
struct Squ{
    LL s[7][7];
};
Squ squ={{1,2,1,4,6,4,1,1,0,0,0,0,0,0,0,0,1,4,6,4,1,0,0,0,1,3,3,1,0,0,0,0,1,2,1,0,0,0,0,0,1,1,0,0,0,0,0,0,1}};  //注意像这样的结构体初始化,只能在它声明的时候
Squ e;

Squ mul(Squ a,Squ b){  //模板
    Squ ans;
    for(int i=0;i<7;i++)
    for(int j=0;j<7;j++){
        ans.s[i][j]=0;
        for(int k=0;k<7;k++){
            ans.s[i][j]+=a.s[i][k]*b.s[k][j];
            ans.s[i][j]%=mod;
        }
    }
    return ans;
}
Squ squickpow(Squ a,LL k){ //模板
    Squ ans=e;
    while(k){
        if(k&1) ans=mul(ans,a);
        a=mul(a,a);
        k/=2;
    }
    return ans;
}

int main(){
    for(int i=0;i<7;i++)
        for(int j=0;j<7;j++)
            if(i==j) e.s[i][j]=1;
            else e.s[i][j]=0;
    LL a,b,n;
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%lld%lld%lld",&n,&a,&b);
        a%=mod;
        b%=mod;
        if(n==1) printf("%lld\n",a);
        else if(n==2) printf("%lld\n",b);
        else{
            Squ aa=squickpow(squ,n-2); //想一想,为什么是n-2
            LL f0[7]={b,a,16,8,4,2,1};
            LL fn[7];
            for(int i=0;i<7;i++){
                fn[i]=0;
                for(int j=0;j<7;j++){
                    fn[i]+=aa.s[i][j]*f0[j];
                    fn[i]%=mod;
                }
            }
            printf("%lld\n",fn[0]%mod);
        }
    }
    return 0;
}

找思路,套模板

B-How many ways

题意: 知道n个点,输入m个有向边,给定s,e,k(0 < n <= 20, m <= 100,k < 20),求从s到e且经过k条边的路线 有多少种方案
思路: 就是离散数学中学的邻接矩阵, 设给的初始有向边的矩阵为 C,则C的n次方中,s,e对应矩阵的点c’[s][e]表示从s到e长度为k的路线个数。注意一条边可以走多次。
所以整理一下,只要知道初始矩阵的k次幂后,对应c’[s][e]的大小就是所求的解了~

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
using namespace std;
struct Squ{
    int s[25][25];
};
Squ e,squ;
int n,m;
const int mod=1000;

Squ mul(Squ a,Squ b){  //模板,矩阵相乘
    Squ squ;
    for(int i=0;i<n;i++)
    for(int j=0;j<n;j++){
        squ.s[i][j]=0;
        for(int k=0;k<n;k++)
            squ.s[i][j]+=a.s[i][k]*b.s[k][j];
            squ.s[i][j]%=mod;
    }
    return squ;
}

Squ squickpow(Squ squ,int k){ //模板,矩阵快速幂 精髓
    Squ ans=e;
    while(k){
        if(k&1)
            ans=mul(ans,squ);
        k/=2; squ=mul(squ,squ);
    }
    return ans;
}

int main(){
    for(int i=0;i<25;i++)
        for(int j=0;j<25;j++)
            if(i==j) e.s[i][j]=1;
            else e.s[i][j]=0;   //初始化单位矩阵

    while(~scanf("%d%d",&n,&m)&&n){
        memset(squ.s,0,sizeof(squ.s));
        for(int i=0;i<m;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            squ.s[a][b]=1;
        }
        int t;
        scanf("%d",&t);
        while(t--){
            int s,e,k;
            scanf("%d%d%d",&s,&e,&k);  //所以正式解题就这三步啊orz
            Squ a=squickpow(squ,k); //
            int res=a.s[s][e]%mod; //
            printf("%d\n",res);
        }
    }
    return 0;
}

Matrix Power Series

题意: 给一个n*n,(n ≤ 30) 的矩阵A,和次数 k (k ≤ 109) 模数m (m < 104),最后求如图的矩阵S,矩阵中每个元素取余m
这里写图片描述
思路:

思路一:二分

这里写图片描述

思路二:矩阵套矩阵

这里写图片描述

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
struct Squ{
    LL s[35][35];
};
struct mSqu{
    Squ s[2][2];
}msqu;

Squ squ,e,t;
LL n,mod,k;

Squ mul(Squ a,Squ b){  //矩阵乘法
    Squ ans;
    for(int i=0;i<n;i++)
    for(int j=0;j<n;j++){
        ans.s[i][j]=0;
        for(int k=0;k<n;k++){
            ans.s[i][j]+=a.s[i][k]*b.s[k][j];
            ans.s[i][j]%=mod;
        }
    }
    return ans;
}

mSqu mmul(mSqu a,mSqu b){  //元素为矩阵的矩阵乘法,类似矩阵
    mSqu ans;
    for(int i=0;i<2;i++)
    for(int j=0;j<2;j++){
        ans.s[i][j]=t;
        for(int k=0;k<2;k++){
            Squ temp=mul(a.s[i][k], b.s[k][j]);
            for(int ii=0;ii<n;ii++){
                for(int jj=0;jj<n;jj++){
                    ans.s[i][j].s[ii][jj]+=temp.s[ii][jj];
                    ans.s[i][j].s[ii][jj]%=mod;
                }
            }
        }
    }
    return ans;
}

mSqu msquick(mSqu a,int k){  //元素为矩阵的矩阵快速乘法
    mSqu ans;

    for(int i=0;i<2;i++)
        for(int j=0;j<2;j++)
            if(i==j) ans.s[i][j]=e;
            else ans.s[i][j]=t;
    while(k){
        if(k&1)
            ans=mmul(ans,a);  
            a=mmul(a,a);
            k/=2;
        }
    return ans;
}

int main(){  //只有一组数据
    scanf("%lld%lld%lld",&n,&k,&mod);
    for(int i=0;i<n;i++)
    for(int j=0;j<n;j++){
        scanf("%lld",&squ.s[i][j]);
        t.s[i][j]=0;
        if(i==j) e.s[i][j]=1;
        else e.s[i][j]=0;
    }
    msqu.s[0][0]=squ;
    msqu.s[0][1]=msqu.s[1][1]=e;
    msqu.s[1][0]=t;
    mSqu ans=msquick(msqu,k);
    Squ res=t;

    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
        res.s[i][j]=ans.s[0][0].s[i][j]+ans.s[0][1].s[i][j];
        if(i==j) res.s[i][j]--;  //注意这里-1,因为这种算法多加了一个单位矩阵,需要减去
        res.s[i][j]%=mod;
        if(j) printf(" ");
        printf("%lld",res.s[i][j]);
        }
        printf("\n");
    }
    return 0;
}

用矩阵快速幂打辅助的数学分析题

So Easy!

题意: 给出a,m,b,n, (0< a, m < 2^15, (a-1)^2< b < a^2, 0 < b, n < 2^31),求这里写图片描述
思路:
这里写图片描述

#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
struct Squ{
    LL s[2][2];
};
Squ e={{1,0,0,1}};
Squ squ;
LL a,b,n,mod;

Squ mul(Squ a,Squ b){
    Squ ans;
    for(int i=0;i<2;i++)
    for(int j=0;j<2;j++){
        ans.s[i][j]=0;
        for(int k=0;k<2;k++){
            ans.s[i][j]+=a.s[i][k]*b.s[k][j];
            ans.s[i][j]%=mod;
        }
    }
    return ans;
}

Squ squickpow(Squ squ,int k){
    Squ ans=e;
    while(k){
        if(k&1) ans=mul(ans,squ);
        k/=2;
        squ=mul(squ,squ);
    }
    return ans;
}

int main(){
    while(~scanf("%lld%lld%lld%lld",&a,&b,&n,&mod)){
        a%=mod,b%=mod;
        LL ans=0;
        if(n==1)    ans=2*a%mod;
        else if(n==2) ans=(2*a*a+2*b)%mod;
        else{
            squ.s[0][0]=(2*a)%mod;
            squ.s[0][1]=(b-a*a+mod*mod)%mod; //注意这里的取模  第一次交WA是因为只加了一个mod,只有加mod*mod才可以
            squ.s[1][0]=1;
            squ.s[1][1]=0;
            Squ res=squickpow(squ,n-2);
            LL f1=2*a%mod;
            LL f2=(2*a*a+2*b)%mod;
            ans=res.s[0][0]%mod*f2%mod+res.s[0][1]*f1%mod;
            ans%=mod;
        }
        printf("%lld\n",ans%mod);

    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值