zoukankan      html  css  js  c++  java
  • 数论

    矩阵乘法-----石头游戏

    我做了两个地方的石头游戏,
    upc
    Acwing
    实际上我是想测试一下数据问题,
    我看了网上的代码和题解,,,,,,,发现基本上都是一样的《基本上》

    石头游戏在一个 n 行 m 列 (1≤n,m≤8) 的网格上进行,每个格子对应一种操作序列,操作序列至多有10种,分别用0~9这10个数字指明。

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

    每秒钟,所有格子同时执行各自操作序列里的下一个字符。

    序列中的每个字符是以下格式之一:

    1、数字0 - 9:表示拿0~9个石头到该格子。
    2、NWSE:表示把这个格子内所有的石头推到相邻的格子,N表示上方,W表示左方,S表示下方,E表示右方。
    3、D:表示拿走这个格子的所有石头。

    给定每种操作序列对应的字符串,以及网格中每个格子对应的操作序列,求石头游戏进行了 t 秒之后,石头最多的格子里有多少个石头。

    在游戏开始时,网格是空的。

    输入格式
    第一行4个整数n, m, t, act。

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

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

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

    输入样例:
    1 6 10 3
    011112
    1E
    E
    0
    输出样例:
    3
    样例解释
    样例中给出了三组操作序列,第一个格子执行编号为0的操作序列”1E”,第二至五个格子执行编号为1的操作序列”E”,第六个格子执行编号为2的操作序列”0”。

    这是另一个类似于传送带的结构,左边的设备0间隔地产生石头并向东传送。

    设备1向右传送,直到设备2。

    10秒后,总共产生了5个石头,2个在传送带上,3个在最右边。

    样例模拟
             
    第 11 0 0 0 0 020 1 0 0 0 031 0 1 0 0 040 1 0 1 0 051 0 1 0 1 060 1 0 1 0 171 0 1 0 1 180 1 0 1 0 291 0 1 0 1 2100 1 0 1 0 3 
    

    题解

    算法进阶指南上写的刚开始我没看懂,下面是我自己的理解加上书上写的

    题目可以理解为是一个n∗m的矩阵,在里面进行操作。

    不难发现,操作序列的长度不超过6,那么1~6的最小公倍数是60,(这个不需要说了吧,自己求一下就出来了)

    即每经过60秒,所有操作序列都会重新处于最开始的字符处。(即又循环到了起点)

    那么第k(1≤k≤60)和第k+60 秒肯定是相同的,又或者说第k秒和很多个60秒之后的第k+60*x秒是相同的

    得到了这个结论,我们就可以很容易想到递推,因为这是单调的。

    之后我们就可以运用矩阵乘法来加速运算。

    设状态矩阵为F FF,我们用这样的方法去表示i行j列的石头数:
    F[num(i , j)] , 其中呢num(i , j) = (i - 1) * m + j 注意,这只是一个构造的方法,并不一定非要是这样的,如果你有别的好的,依然可以构造其他的<我自己在上面纠结了很长时间>

    根据题目定义,我们可以得到F的初始状态是[0 ,0 ,0 … 0]
    因为矩阵乘法里面肯定不能全为0 , 否则你怎么乘也是0 ,所以设置设置一个1,位置就在p = n * m + 1 , 也可以再别的地方

    那我们也需要一个转移矩阵A AA来转移状态吧。

    对于第k(1≤k≤60)秒,构造一个转移状态A[k]行,列下标均为1 ~ n * m + 1

    构造方法类似于跑图:

    1. 若网格(i,j)第k秒的操作字符为“N”,且i>1 ,则令A[k][num(i,j),num(i−1,j)]=1
      ​意思就是把石头推到上边的格子。字符"W",“S”,"E"类似。

    2. 若网格(i,j)第k秒的操作字符时一个数字x,则令A[k][0,num(i,j)]=x

    3. 令A[k][p][p] = 1

    4. A[k]的其它部分均赋值为0.

    注意石头来源其实当成了p
    上述结构实际上把“状态矩阵”的第p列作为“石头来源”,A[k][p,p]=1 保证F[p]=1 ,
    A[k][p,num(i,j)]=x 相当于从“石头来源”获取x个石头,放到格子(i,j)上。
    在这里插入图片描述
    另外,对于Ak[num(i,j),num(i−1,j)]=1 其实也是建边,使石头能够“流过去”。

    上述文章部分参考
    博客

    刚开始我的矩阵快速幂是这样写的
    struct node
    {
    	ll a[N][N] ;
    };
    node muti(node x ,node y)
    {
    	node res ;
        memset(res.a,0,sizeof res.a);
    	for(int  i = 0;i < N;i ++)
    	 for(int j = 0 ;j < N;j ++)
    	  for(int k = 0;k < N;k ++)
    	    res.a[i][j] = (res.a[i][j] + x.a[i][k] *y.a[k][j]) % mod ;
    	return res ;
    }
    void fastm(ll b)
    {
    	node res , c ;
    	memset(res.a,0,sizeof res.a);
    	c.a[0][1] = 1 ;
    	c.a[1][0] = 1 ;
    	c.a[1][1] = 0 ;
    	c.a[0][0] = 1 ;
    	for(int i = 0;i <N ;i ++)
    	 res.a[i][i] = 1 ;
    	while(b)
    	{
    		if(b & 1) res = muti(res,c);
    		c =muti(c,c);
    		b >>= 1 ;
    	}
        
    }
    这个应该是最普通的矩阵快速幂了吧,但是我也只好这个,不过今天用的是另一个,但是原理是一样的,大佬们应该都是会的
    
    void muls(ll a[66][66] , ll b[66][66])
    {
    	ll w[66][66] ;
    	memset(w , 0 , sizeof w) ;
    	for(int i = 1;i <= p;i ++)
    	 for(int j = 1;j <= p ;j ++)
    	  for(int k = 1;k <= p;k ++)
    	   w[i][j] += a[i][k] * b[k][j] ;
    	memcpy(a , w , sizeof w) ;
    }
    void mul(ll f[66] , ll a[66][66])
    {
    	ll w[66] ; 
    	memset(w , 0 , sizeof w) ;
    	for(int i = 1;i <= p;i ++)
    	 for(int k = 1;k <= p;k ++)
    	  w[i] += f[k] * a[k][i] ;
    	memcpy(f , w , sizeof w) ;
    }
    

    更多的细节代码中有注释 , 网上大部分都是这个代码,只是没有详细注释

    #include <iostream>
    #include <cstring>
    #include <cmath>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    
    typedef long long ll ;
    int n , m , t , act , p ;
    int num(int i , int j) {return (i - 1) * m + j ;} 
    void muls(ll a[66][66] , ll b[66][66])
    {
    	ll w[66][66] ;
    	memset(w , 0 , sizeof w) ;
    	for(int i = 1;i <= p;i ++)
    	 for(int j = 1;j <= p ;j ++)
    	  for(int k = 1;k <= p;k ++)
    	   w[i][j] += a[i][k] * b[k][j] ;
    	memcpy(a , w , sizeof w) ;
    }
    void mul(ll f[66] , ll a[66][66])
    {
    	ll w[66] ; 
    	memset(w , 0 , sizeof w) ;
    	for(int i = 1;i <= p;i ++)
    	 for(int k = 1;k <= p;k ++)
    	  w[i] += f[k] * a[k][i] ;
    	memcpy(f , w , sizeof w) ;
    }
    // 以上是矩阵乘法部分,
    char b[20][20] , s[100] ;
    ll f[66] , e[66][66][66] , d[66][66] ;
    int a[20][20] , c[20][20] ;
    int main()
    {
    	scanf("%d%d%d%d",&n , &m ,&t , &act) ;
    	for(int i = 1;i <= n;i ++)
    	 {
    	 	scanf("%s",s + 1) ;
    	 	for(int j = 1;j <= m;j ++)
    	 	 a[i][j] = s[j] - '0' + 1 ;
    	 }
    	 // 注意上面将字符串转化为数组的时候加了 1 , 这个是因为下面输入命令的时候是从一开始的
       for(int i = 1;i <= act ;i ++) scanf("%s",b[i]) ;
    	 p = n * m + 1 ; // ** 此时的p是 石头来源 ,
    	 for(int k = 1;k <= 60 ;k ++)  // 60秒一循环 , 处理每一秒的所有矩阵
    	  {
    	  	for(int i = 1;i <= n;i ++)
    	  	 for(int j = 1;j <= m;j ++)
    	  	  {
    	  	  	int x = a[i][j] , y = c[i][j] ; 
    // x 表示上 当前格子用的是那一条命令 , y 表示每一秒使用的当前命令里面的第几个字符 , 在下面循环结束的时候再次加一取模,很是神奇,不懂的可以模拟一下,
    	  	  	if(isdigit(b[x][y])) // 当前命令这个该时间使用的字符
    	  	  	 {
    	  	  	 	e[k][p][num(i , j)] = b[x][y] - '0' ;
    	  	  	 	e[k][num(i , j)][num(i , j)] = 1 ;
    			 }
    			 else if(b[x][y] == 'N' && i > 1) e[k][num(i , j)][num(i - 1 , j)] = 1 ;
    			 else if(b[x][y] == 'W' && j > 1) e[k][num(i , j)][num(i , j - 1)] = 1 ;
    			 else if(b[x][y] == 'S' && i < n) e[k][num(i , j)][num(i + 1 , j)] = 1 ;
    			 else if(b[x][y] == 'E' && j < m) e[k][num(i , j)][num(i , j + 1)] = 1 ;
    			 c[i][j] = (y + 1) % strlen(b[x]) ; // 此时就是加一取模,那么下一秒 也就是k + 1 秒使用的字符就是y+1 % strlen(b[x]) , 可以仔细想一想
    		  }
    		  e[k][p][p] = 1 ;
    	  }
    	  // 下面分成了两部分 ,可以想象t时间可能是很多个60组成的,然后如果还是一个一个的乘,很浪费时间,所以,第一步就是乘一次就相当于乘了60秒的
    	  memcpy(d , e[1] , sizeof e[1]) ;
    	  f[p] = 1 ;
    	  for(int k = 2;k <= 60;k ++) muls(d , e[k]) ; // 循环结束,d表示的就是60秒的矩阵
    	  ll ans = 0 ;
    	  int w = t / 60 ;
    	  while(w) // 这个快速幂用d乘的 ,也就是用60秒的矩阵乘的,结果放在了f矩阵上
    	  {
    	  	if(w & 1) mul(f , d) ;
    	  	muls(d , d) ; 
    	  	w >>= 1 ;
    	  }
    	  // 下面的是乘的一秒的矩阵, 然后求解最大值
    	  w = t % 60 ;
    	  for(int i = 1;i <= w;i ++) mul(f , e[i]) ;
    	  for(int i = 1;i < p;i ++) ans = max(ans , f[i]) ;
    	  printf("%lld\n",ans) ;
    	  
    	  return 0 ;
    }
    
  • 相关阅读:
    wampServer多站点配置
    wamp中根目录自定义
    [学习笔记] 七步从Angular.JS菜鸟到专家(2):Scopes [转]
    [学习笔记] 七步从Angular.JS菜鸟到专家(1):如何开始 [转]
    [学习笔记] Python标准库简明教程 [转]
    [学习笔记] Python标准库的学习准备 [转]
    [学习笔记] socket通信简介 [转]
    [学习笔记] 国内主流php框架比较 [转]
    [学习笔记] symfony核心类 [转]
    [学习笔记] Symfony2安全机制
  • 原文地址:https://www.cnblogs.com/spnooyseed/p/12870928.html
Copyright © 2011-2022 走看看