zoukankan      html  css  js  c++  java
  • 【浮*光】#矩阵乘法# 矩阵优化的学习与练习

    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]超级跳马 // 奇偶行分类讨论,得出递推方程

     

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

  • 相关阅读:
    案例19-页面使用ajax显示类别菜单
    案例18-首页最新商品和热门商品显示
    案例17-validate自定义校验规则校验验证码是否输入正确
    案例16-validate自定义校验规则校验用户名是否存在
    案例15-基本的表单校验使用validate
    测开之路六十九:监控平台之视图层
    测开之路六十八:监控平台之监控逻辑和处理逻辑
    测开之路六十七:监控平台之附加功能准备
    测开之路六十六:UI测试平台之处理逻辑和蓝图添加到程序入口
    测开之路六十五:UI测试平台之js
  • 原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10512899.html
Copyright © 2011-2022 走看看