【浮*光】#矩阵乘法# 矩阵优化的学习与练习

T:【p4838】p哥破解密码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;

/*【p4838】p哥破解密码 //煞笔题我调了一个小时 /哭哭...
定义一个串合法,当且仅当串只由A和B构成,且没有连续的3个A。
密码就是长度为N的合法字符串数量对19260817取模的结果。*/

/*【分析】f[i][0]=f[i-1][0]+f[i-1][1]+f[i-1][2];
f[i][1]=f[i-1][0],f[i][2]=f[i-1][1]; f[1][0]=f[1][1]=1; */

/* 
得到dp矩阵:1 1 0
           1 0 1
           1 0 0 
那么答案就是:矩阵(1,1,0)*dp矩阵^(n-1)得到的a.m[1][1]+a.m[1][2]+a.m[1][3]。*/

//【矩阵快速幂】给定n*n的矩阵A,求A^k。

void reads(ll &x){ //读入优化(正负整数)
    ll fx=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=fx; //正负号
}

const ll mod=19260817;

struct Mat{ ll m[9][9]; }a,dp; //a是原始矩阵

Mat mat_mul(Mat x,Mat y){ //矩阵乘:x*y=c
    Mat c; memset(c.m,0,sizeof(c.m)); //记得要随时清空
    for(ll i=1;i<=3;i++) for(ll j=1;j<=3;j++) for(ll k=1;k<=3;k++)
        c.m[i][j]=(c.m[i][j]%mod+x.m[i][k]*y.m[k][j]%mod)%mod; return c; }

Mat mat_pow(Mat x,ll y) //矩阵快速幂:ans*x^y
 {  Mat ans; memset(ans.m,0,sizeof(ans.m)); //记得要随时清空
    for(ll p=1;p<=3;p++) ans.m[p][p]=1; //初始化为单位矩阵(一定不能忘记233)
    while(y){ if(y&1) ans=mat_mul(ans,x); x=mat_mul(x,x); y>>=1; } return ans; }

void init()
 { memset(a.m,0,sizeof(a.m)); a.m[1][1]=1,a.m[1][2]=1,a.m[1][3]=0;
   memset(dp.m,0,sizeof(dp.m)); dp.m[1][1]=1,dp.m[1][2]=1,dp.m[2][1]=1,dp.m[2][3]=1,dp.m[3][1]=1; }

int main(){ 
    ll T,n; reads(T); while(T--){
        reads(n); init(); Mat tmp=mat_pow(dp,n-1); a=mat_mul(a,tmp);
        cout<<(a.m[1][1]+a.m[1][2]+a.m[1][3])%mod<<endl; }
}
【p4838】p哥破解密码 // 简单题

T:【p4910】帕秋莉的手环

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;

/*【p4910】帕秋莉的手环 */

/*【分析】f[i][0]=f[i-1][0]+f[i-1][1](=f[i-2][0]); 
f[i][1]=f[i-1][0]; f[1][0]=f[1][1]=1; 发现这其实是一个斐波那契数列。
所以答案为:求斐波那契数列 第n项 + 第n-1项的两倍 。*/

/* 得到dp矩阵:1 1 
              0 1 。*/


//  3 4  乘  递推矩阵  =  4 7 (手算也行)

//  解上述方程得递推矩阵为: 

//             0  1
//  3 4  乘            =  4 7
//             1  1

void reads(ll &x){ //读入优化(正负整数)
    ll fx=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=fx; //正负号
}

const ll mod=1000000007;

struct Mat{ ll m[9][9]; }a,dp; //a是原始矩阵

Mat mat_mul(Mat x,Mat y){ //矩阵乘:x*y=c
    Mat c; memset(c.m,0,sizeof(c.m)); //记得要随时清空
    for(ll i=1;i<=2;i++) for(ll j=1;j<=2;j++) for(ll k=1;k<=2;k++)
        c.m[i][j]=(c.m[i][j]%mod+x.m[i][k]*y.m[k][j]%mod)%mod; return c; }

Mat mat_pow(Mat x,ll y) //矩阵快速幂:ans*x^y
 {  Mat ans; memset(ans.m,0,sizeof(ans.m)); //记得要随时清空
    for(ll p=1;p<=2;p++) ans.m[p][p]=1; //初始化为单位矩阵(一定不能忘记233)
    while(y){ if(y&1) ans=mat_mul(ans,x); x=mat_mul(x,x); y>>=1; } return ans; }

void init()
 { memset(dp.m,0,sizeof(dp.m)); dp.m[1][1]=0,dp.m[1][2]=dp.m[2][1]=dp.m[2][2]=1;
   memset(a.m,0,sizeof(a.m)); a.m[1][1]=2,a.m[1][2]=1,a.m[2][1]=a.m[2][2]=0; }

int main(){ 
    ll T,n; reads(T); while(T--){
        reads(n); init(); Mat tmp=mat_pow(dp,n-1); a=mat_mul(a,tmp);
        cout<<(a.m[1][2])%mod<<endl; } //这个位置就是 第n项 + 第n-1项的两倍
}
【p4910】帕秋莉的手环 // 简单题

T:【sp2742】SumSums

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;

/*【sp2742】SumSums
长度为N的整数序列C,进行T次操作,每次把序列每一位修改为'其他各位'的和。 */

/*  每一对 原数 和 加密后的数 构成一个矩阵,即矩阵a:[ci,sum-ci]。
    则加密一次后的矩阵A为[sum-ci,(n-1)sum-(sum-ci)]。
    可以发现:所有数加密一次后总和变为原来的n-1倍。
    推出 a 乘 dp矩阵[[0,n-1],[1,n-2]] 可以得到 A。
    按照这个规律,加密T次和T+1次构成的矩阵为a*b^t。
    对于每个数,处理出n个矩阵a,就可以用快速幂解决a*bt,得到加密t次的数。*/

void reads(ll &x){ //读入优化(正负整数)
    ll fx=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=fx; //正负号
}

const ll mod=98765431; ll c[50019],sum=0;

struct Mat{ ll m[9][9]; }a,dp;

Mat mat_mul(Mat x,Mat y){ //矩阵乘:x*y=c
    Mat c; memset(c.m,0,sizeof(c.m)); //记得要随时清空
    for(ll i=1;i<=2;i++) for(ll j=1;j<=2;j++) for(ll k=1;k<=2;k++)
        c.m[i][j]=(c.m[i][j]%mod+x.m[i][k]*y.m[k][j]%mod)%mod; return c; }

Mat mat_pow(Mat x,ll y) //矩阵快速幂:ans*x^y
 {  Mat ans; memset(ans.m,0,sizeof(ans.m)); //记得要随时清空
    for(ll p=1;p<=2;p++) ans.m[p][p]=1; //初始化为单位矩阵(一定不能忘记233)
    while(y){ if(y&1) ans=mat_mul(ans,x); x=mat_mul(x,x); y>>=1; } return ans; }

int main(){ 
    ll n,t; reads(n),reads(t); //T次操作 
    for(ll i=1;i<=n;i++) reads(c[i]),sum=(sum+c[i])%mod;
    dp.m[1][1]=0,dp.m[1][2]=n-1,dp.m[2][1]=1,dp.m[2][2]=n-2;
    dp=mat_pow(dp,t); //先求出dp矩阵的T次方
    for(ll i=1;i<=n;i++){ a.m[2][1]=a.m[2][2]=0;
        a.m[1][1]=c[i],a.m[1][2]=(sum-c[i]+mod)%mod;
        printf("%lld
",mat_mul(a,dp).m[1][1]);
    }
}
【sp2742】SumSums // 技巧 + 较复杂数学计算

T:【p3702】序列计数

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<map>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;

//【p3702】序列计数
//符合要求:序列中的数都是不超过m的正整数,且这n个数的和是p的倍数。
//Alice还希望,这n个数中,至少有一个数是质数。有多少个序列满足她的要求。

//至少有1个质数的方案数=总方案数-不含质数的方案数
//可以先在O(m)的时间内把1~m的质数筛出来。
//然后考虑:从mod p=0到mod p=0,可以由mod p=a和mod p=p-a两个阶段组成。
//设f[i][j]表示从mod p=i到mod p=j的方案数,这是一个矩阵,自乘m次就能得到答案。

//对于每个数可以更新所有的f[i](0≤i<n),不过这样会TLE。
//因为f都是循环出现的,只需要求f[0]即可,再据此推其余的f。

void reads(int &x){ //读入优化(正负整数)
    int fa=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fa; //正负号
}

const int mod=20170408;

bool vis_[20000019]; int primes[5000019],cnt;

struct Mat{ int n,m; ll num[110][110];
    Mat(){ n=m=0; memset(num,0,sizeof(num)); }
    Mat operator*(const Mat a)const{
      Mat ans; ans.n=n,ans.m=a.m; int i,j,k;
      for(i=1;i<=ans.n;i++) for(j=1;j<=ans.m;j++) for(k=1;k<=m;k++)
        ans.num[i][j]=(ans.num[i][j]+num[i][k]*a.num[k][j])%mod; return ans; } }A,B;

Mat mat_pow(Mat x,int y)
 { Mat ans; ans.n=x.n,ans.m=x.m; for(int i=1;i<=ans.n;i++) ans.num[i][i]=1;
   while(y){ if(y&1) ans=ans*x; x=x*x,y>>=1; } return ans; }

int main(){
    int n,m,p; reads(n),reads(m),reads(p); vis_[1]=1;
    for(int i=2;i<=m;i++){ //欧拉线性筛质数
        if(!vis_[i]) primes[++cnt]=i;
        for(int j=1;j<=cnt&&i*primes[j]<=m;j++)
         { vis_[i*primes[j]]=1; if(i%primes[j]==0) break; }
    } A.n=A.m=B.n=B.m=p; //设置为p*p的矩阵
    for(int i=1;i<=m;i++){ A.num[p][(i-1)%p+1]++; //先推p
        if(vis_[i]) B.num[p][(i-1)%p+1]++; } //所有数 vs 合数
    for(int i=p-1;i>=1;i--) for(int j=1;j<=p;j++) //线性逆推
        A.num[i][j]=A.num[i+1][j%p+1],B.num[i][j]=B.num[i+1][j%p+1];
    printf("%lld
",(mat_pow(A,n).num[p][p]-mat_pow(B,n).num[p][p]+mod)%mod);
}
【p3702】序列计数 // n个数的和是p的倍数 + 至少含有一个质数 的方案数

T:【p2461】递归数列

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;

/*【p2461】递归数列
对于i<=k:ai=bi;对于i>k:ai=c1*ai-1+c2*ai-2+...+ck*ai-k。
给定自然数m<=n, 计算am+a(m+1)+a(m+2)+...+an, 并输出它%p的余数的值。*/

/* 对于矩阵[a1 a2 ... ak],按照题目中的公式推出[a2 a3 ... ak+1]。
   将区间求和转化为前缀相减处理(注意此时不要改变dp矩阵a,需要特判t<=k)。*/
   
//真·调了一个下午,矩阵题就是这么...emm...

//最后是因为在 int main() 里也写了一个 ll p; ...一直re

void reads(ll &x){ //读入优化(正负整数)
    ll fx_=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fx_; //正负号
}

ll n,p,sum[20];

struct Mat{ ll v[20][20]; //矩阵的数值v[][]
    Mat(){ memset(v,0,sizeof(v)); }
    Mat operator*(const Mat a)const{ Mat ans; ll i,j,k;
      for(i=1;i<=n;i++) for(j=1;j<=n;j++) for(k=1;k<=n;k++)
        ans.v[i][j]=(ans.v[i][j]+v[i][k]*a.v[k][j]%p)%p; return ans; } }A,B;

Mat mat_pow(Mat x,ll y)
 { Mat ans; for(ll i=1;i<=n;i++) ans.v[i][i]=1;
   while(y){ if(y&1) ans=ans*x; x=x*x,y>>=1; } return ans; }

ll cal(ll x){ if(x<n) return sum[x]; return (B*mat_pow(A,(x-n+1))).v[1][n]; }

int main(){
    reads(n); for(int i=1;i<=n;i++) reads(B.v[1][i]), //n+1行存前缀和
        B.v[1][n+1]=B.v[1][n+1]+B.v[1][i],sum[i]=sum[i-1]+B.v[1][i];
    for(int i=n;i>=1;i--) reads(A.v[i][n]),A.v[i][n+1]=A.v[i][n];
    for(int i=1;i<n;i++) A.v[i+1][i]=1; n++; A.v[n][n]=1;
    ll l,r; reads(l),reads(r),reads(p);
    for(int i=1;i<n;i++) sum[i]%=p;
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) 
        A.v[i][j]%=p,B.v[i][j]%=p;
    printf("%lld
",(cal(r)-cal(l-1)+p)%p);
}
【p2461】递归数列 // 求 ai=c1*ai-1+c2*ai-2+...+ck*ai-k 的前缀和

T:【p2151】HH去散步

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;

/*【p2151】HH去散步
N个点M条边的无向图,A->B。不能立刻沿着上次边的反方向走。求距离为t方案数。*/

/*【分析】把每条边看作行列序号。则(i,j)表示:从一条边走到另一条边的方案数。
对于两条边i,j,如果不是刚走过来的边并且能够接上(i.y=j.x),则Aij=1。*/

void reads(int &x){ //读入优化(正负整数)
    int fx_=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fx_; //正负号
}

const int p=45989,N=159; int n,m,k,s,t,px[N],py[N];

struct Mat{ int v[N][N]; //矩阵的数值v[][]
    Mat(){ memset(v,0,sizeof(v)); }
    Mat operator*(const Mat a)const{ Mat ans; int i,j,k;
      for(i=0;i<=m;i++) for(j=0;j<=m;j++) for(k=0;k<=m;k++)
        ans.v[i][j]=(ans.v[i][j]+v[i][k]*a.v[k][j]%p)%p; return ans; } }a;

Mat mat_pow(Mat x,int y)
 { Mat ans; for(int i=0;i<=m;i++) ans.v[i][i]=1;
   while(y){ if(y&1) ans=ans*x; x=x*x,y>>=1; } return ans; }

int main(){
    reads(n),reads(m),reads(k),reads(s),reads(t);
    for(int i=1;i<=m;i++) reads(px[i<<1]),reads(py[i<<1]), 
        px[i<<1|1]=py[i<<1],py[i<<1|1]=px[i<<1]; //反向边
    m=m*2+1; for(int i=2;i<=m;i++){
        if(px[i]==s) a.v[0][i]=1; if(py[i]==t) a.v[i][1]=1;
        for(int j=2;j<=m;j++) //(i.y=j.x),可以接上这个边
            if(py[i]==px[j]&&(i^j)!=1) a.v[i][j]=1; //不是同一条边
    } a=mat_pow(a,k+1); printf("%d
",a.v[0][1]);
}
【p2151】HH去散步 // 分析‘边’ + 图上矩阵算法求方案数

T:【p2151】迷路

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;

/*【p2151】迷路
n个点的有向图,每条边的权值都在[1,9]之间。求‘从1到n经过路径边权和恰好为t’的方案数模2009。*/

/*【分析】考虑拆点,将1个点拆成9个连成链,每次将‘入点i对应边权的点’与‘出点j’相连。*/

//https://www.cnblogs.com/GXZlegend/p/8302711.html

void reads(int &x){ //读入优化(正负整数)
    int fx_=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
    x*=fx_; //正负号
}

const int p=2009,N=109; int n,m,k;

struct Mat{ int v[N][N]; //矩阵的数值v[][]
    Mat(){ memset(v,0,sizeof(v)); }
    Mat operator*(const Mat a)const{ Mat ans; int i,j,k;
      for(i=0;i<=m;i++) for(j=0;j<=m;j++) for(k=0;k<=m;k++)
        ans.v[i][j]=(ans.v[i][j]+v[i][k]*a.v[k][j]%p)%p; return ans; } }A;

Mat mat_pow(Mat x,int y)
 { Mat ans; for(int i=0;i<=m;i++) ans.v[i][i]=1;
   while(y){ if(y&1) ans=ans*x; x=x*x,y>>=1; } return ans; }

int main(){
    reads(n),reads(k); m=n*9; char ss[19];
    for(int i=0;i<n;i++){ scanf("%s",ss);
        for(int j=0;j<8;j++) A.v[i*9+j][i*9+j+1]=1;
        for(int j=0;j<n;j++) //↑↑拆成的9个点连成一条链
            if(ss[j]!='0') A.v[i*9+ss[j]-'1'][j*9]=1; 
        //有向图(i,j)连边,选取入点i的第ss[j]-'1'个记录边权,出点在j*9位置
    } printf("%d
",mat_pow(A,k).v[0][n*9-9]); // 0 -> n-1
}
【p2151】迷路 // 边权在[1,9]之间,边权和为t:每个点拆成9个点

T:【p3990】超级跳马

#include <cstdio>
#include <cstring>
#include <algorithm>
#define mod 30011
using namespace std;

//【p3990】[Shoi2013]超级跳马 //矩阵乘法

int n; //见:https://www.cnblogs.com/GXZlegend/p/7816112.html

struct data{
    int v[105][105];
    data() {memset(v , 0 , sizeof(v));}
    int *operator[](int a) {return v[a];}
    data operator*(data &a){
        data ans; int i , j , k;
        for(i=1;i<=n;i++) for(j=1;j<=n;j++) for(k=1;k<=n;k++)
            ans[i][j]=(ans[i][j]+v[i][k]*a[k][j]) % mod; return ans;
    }
}I , A , B;

data pow(data x , int y){
    data ans;
    for(int i = 1 ; i <= n ; i ++ ) ans[i][i] = 1;
    while(y){
        if(y & 1) ans = ans * x;
        x = x * x , y >>= 1;
    } return ans;
}

int main(){
    int m , i; scanf("%d%d" , &n , &m);
    for(i = 1 ; i <= n ; i ++ ) I[i][i] = I[i + n][i] = I[i][i + n] = 1;
    for(i = 1 ; i < n ; i ++ ) I[i + 1][i] = I[i][i + 1] = 1;
    n <<= 1 , A = pow(I , m - 2) , B = A * I;
    printf("%d
" , (B[1][n >> 1] - A[1][n] + mod) % mod);
}
【p3990】[Shoi2013]超级跳马 // 奇偶行分类讨论,得出递推方程

 

                                                ——时间划过风的轨迹,那个少年,还在等你

原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10512899.html