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 ;
    }
    
  • 相关阅读:
    Mvc+三层(批量添加、删除、修改)
    js中判断复选款是否选中
    EF的优缺点
    Git tricks: Unstaging files
    Using Git Submodules
    English Learning
    wix xslt for adding node
    The breakpoint will not currently be hit. No symbols have been loaded for this document."
    Use XSLT in wix
    mfc110ud.dll not found
  • 原文地址:https://www.cnblogs.com/spnooyseed/p/12870928.html
Copyright © 2011-2022 走看看