zoukankan      html  css  js  c++  java
  • 矩阵乘法

    矩阵乘法

    定义C是由A,B两个矩阵相乘得到 若A是n行k列,B是k行m列 所以C是n行m列

    (C_{i,j}=sum_{k=1}^mA_{i,k}*b_{k,j}) 用我自己的话来说就是C的i行j列是由 A的i行*B的j列

    矩阵乘法满足结合律,分配律,但不满足交换律(显然,如果交换后C的形态都可能发生变换)

    如果A是1行n列,B是n行n列 那么C是1行n列 那么(C_{j}=sum_{k=1}^{n}A_k*B_{k,j}) 这个式子在矩阵快速幂的时候会经常用到 用可以听懂的话来说 这个式子表示C的第j个数=A全部*B的第j列 具体怎么用等下来讲

    CH3401 石头游戏

    一道矩阵快速幂的板子题。QAQ

    首先一般状态矩阵为一维的 我们可以显然的令(i行,j列)表示的数值为((i-1)*m+j),这样就可以通过这个式子转成一维的了

    令A为转移矩阵,F为状态矩阵 (A_{k,i,j})为k时刻i行j列 (F_i)为第i行 我们可以列出以下式子

    首先令(A[k][0][0]=1)

    当操作为数字时令 (A[k][calc(i,j)][calc(i,j)]=1,A[k][0][calc(i,j)]=digit)

    当操作为字母时 若表示转移 则(A[k][calc(i,j)][calc(i-1,j)]=1)这种类似的式子

    若操作表示删除 则(A[k][calc(i,j)][calc(i,j)]=0)

    可能到现在还是很懵逼为什么要这样定义转移矩阵 但是感性理解一下就会发现(A[k][calc(i,j)][calc(x,y)])不就表示(F[calc(x,y)]=sum F[calc(i,j)]*A[k][calc(i,j)][calc(x,y)]) 相当于将在(calc(i,j))位置的数乘上(A[k][calc(i,j)][calc(x,u)])吗?再返回上面的式子理解一下 当操作为数字时 相当于将自己原封不动的额复制一份 并且加上数字(我们令(A[K][0][0]=1),将他在(calc(i,j))位置上乘上(digit),相当于加上了(digit));当操作为字母切表示转移时,相当于将自己改成0,并且将自己的值付给周围的位置。而我们没有写将自己改成0 是因为初始化的时候就已经赋为0了;当表示删除 就是将自己改成0

    可是讲了这么久好像和矩阵快速幂没有关系?

    这道题需要注意的就是 (1)~(6)的最小公倍数为60 相当于经过60S必定会重复与上一个60秒。这样就可以用一个矩阵来表示这60S的总变化,然后就矩阵快速幂啊QAQ。

    写一下式子 B表示60S的总变化矩阵 A表示转移矩阵 F表示状态矩阵 令(q=t/60) (r=t%60)

    (F_t=F_0*B^q*prod_{i=1}^rAi) 然后就可以啦

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #define LL long long 
    using namespace std;
    LL read(){
    	LL x=0,f=1;char ch=getchar();
    	while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}
    	return x*f;	
    }
    LL n,m,t,act;
    LL F[100];
    string opti[10],oper[10];
    LL A[70][100][100],B[100][100];
    LL calc(LL x,LL y){
    	return (x-1)*m+y;
    }
    void mulqaq(LL a[100][100],LL b[100][100]){
    	LL c[100][100];
    	memset(c,0,sizeof(c));
    	for(LL i=0;i<70;i++)
    		for(LL j=0;j<70;j++)
    			for(LL k=0;k<70;k++)
    				c[i][j]+=a[i][k]*b[k][j];
    	memcpy(a,c,sizeof(c));
    }
    void prepare_work(){
    	F[0]=1;
    	for(LL times=0;times<60;times++){
    		A[times][0][0]=1;	
    		for(LL i=1;i<=n;i++){
    			for(LL j=1;j<=m;j++){
    				LL qaq=opti[i-1][j-1]-'0';
    				LL shawn=times%oper[qaq].size();
    				char ch=oper[qaq][shawn];
    				if(ch>='0' && ch<='9'){
    					A[times][calc(i,j)][calc(i,j)]=1;
    					A[times][0][calc(i,j)]=ch-'0';
    				}
    				else{
    					if(ch=='N'){
    						if(i-1>=1)
    							A[times][calc(i,j)][calc(i-1,j)]=1;	
    					}
    					else if(ch=='W'){
    						if(j-1>=1)
    							A[times][calc(i,j)][calc(i,j-1)]=1;
    					}
    					else if(ch=='E'){
    						if(j+1<=m)
    							A[times][calc(i,j)][calc(i,j+1)]=1;
    					}
    					else if(ch=='S'){
    						if(i+1<=n)
    						A[times][calc(i,j)][calc(i+1,j)]=1;
    					}
    					else A[times][calc(i,j)][calc(i,j)]=0;
    				}	
    			}
    		}
    	}
    	for(int i=0;i<70;i++)
    		B[i][i]=1;
    	for(int i=0;i<60;i++){
    		mulqaq(B,A[i]);
    	}
    }
    void mul(LL f[100],LL b[100][100]){
    	LL c[100];memset(c,0,sizeof(c));
    	for(LL j=0;j<70;j++){
    		for(LL k=0;k<70;k++){
    			c[j]+=f[k]*b[k][j];
    		}
    	}
    	memcpy(f,c,sizeof(c));
    }
    void mulself(LL a[100][100]){
    	LL c[100][100];memset(c,0,sizeof(c));
    	for(LL i=0;i<70;i++){
    		for(LL j=0;j<70;j++){
    			for(LL k=0;k<70;k++){
    				c[i][j]+=a[i][k]*a[k][j];
    			}
    		}
    	}
    	memcpy(a,c,sizeof(c));
    }
    int main(){
    	n=read();m=read();t=read();act=read();
    	for(LL i=0;i<n;i++) cin >> opti[i];
    	for(LL i=0;i<act;i++) cin >> oper[i];
    	prepare_work(); 
    	LL q=t/60;
    	LL r=t%60;
    	while(q){
    		if(q&1) mul(F,B);
    		mulself(B);q>>=1;
    	}
    	for(int i=0;i<r;i++){
    		mul(F,A[i]);
    	}
    	LL ans=0;
    	for(int i=1;i<=70;i++)
    		ans=max(ans,F[i]);
    	printf("%lld",ans);
    	return 0;
    }
    

    做了矩阵的题在慢慢补充吧(我自己都不信)

    迷宫 SCOI2009

    这道题运用了一个矩阵乘法的性质: 对于一个图,若其邻接矩阵只有0或1,则该矩阵的t次幂可以表示为走了t步的方案数

    然而这道题两个点之间的距离有可能大于1,因此我们可以建虚点(使得经过某条边权不为1的边,可以转换为经过某些边权为1的虚边,切经过了那些边就可以相当于经过了某条边权不为1的边。QAQ)

    如何建虚边?将一个点拆成九个点(莫名像网络流毒瘤建图),看做有9层,然后虚点依次向上一个点连边,直到上一个点没有。 对于边权为(x),从(i到j)的边可以连i的第一层的点,j的第(x-1)层点,边权为1的边

    至于为什么是j的第(x-1)层。。因为我们本身就连了一条边权为1的边啊

    然后建完图之后就可以跑矩阵快速幂了QAQ 注意(t--) 因为我初始化的转移矩阵就是多带了一次的。当然也可以将初始的转移矩阵设为单位矩阵 然后t就不用(--)

    还有就是注意要取模

    #include <cstdio>
    #include <iostream>
    #include <cstring>
    #include <algorithm>
    #define LL long long 
    using namespace std;
    const LL maxn=1000,mod=2009;
    LL f[maxn][maxn],n,t,base[maxn][maxn];
    LL calc(LL x,LL y){
    	return x+y*n;
    }
    void mul(LL a[maxn][maxn],LL b[maxn][maxn]){
    	LL c[maxn][maxn];memset(c,0,sizeof(c));
    	for(LL i=1;i<=10*n;i++){
    		for(LL j=1;j<=10*n;j++){
    			for(LL k=1;k<=10*n;k++){
    				c[i][j]=(c[i][j]+a[i][k]*b[k][j]%mod)%mod;
    			}
    		}
    	}
    	memcpy(a,c,sizeof(c));
    }
    void mulself(LL a[maxn][maxn]){
    	LL c[maxn][maxn];
    	memset(c,0,sizeof(c));
    	for(LL i=1;i<=10*n;i++){
    		for(LL j=1;j<=10*n;j++){
    			for(LL k=1;k<=10*n;k++){
    				c[i][j]=(c[i][j]+a[i][k]*a[k][j]%mod)%mod;
    			}
    		}
    	}
    	memcpy(a,c,sizeof(c));
    }
    int main(){
    	scanf("%lld %lld",&n,&t);
    	for(LL i=1;i<=n;i++){
    		for(LL j=1;j<=8;j++){
    			f[calc(i,j)][calc(i,j-1)]=1;
    		}
    		for(LL j=1;j<=n;j++){
    			int digit;
    			scanf("%1d",&digit);
    			if(digit==0) continue;
    			f[i][calc(j,digit-1)]=1;
    		}
    	}
    	memcpy(base,f,sizeof(f));
    	t-=1;
    	while(t){
    		if(t&1) mul(f,base);
    		mulself(base);t>>=1;
    	}
    	printf("%lld",f[1][n]%mod);
    	return 0;
    }
    
  • 相关阅读:
    python中字典一键多相同值反转技巧
    win10下安装mysql
    上台阶问题的具体走法用python来实现
    桥接模式
    适配器模式
    多线程中lock的使用
    原型模式
    多线程
    建造者模式
    代理模式
  • 原文地址:https://www.cnblogs.com/mendessy/p/11791841.html
Copyright © 2011-2022 走看看