[ZJOI2005]沼泽鳄鱼 矩阵乘法

~~~题面~~~

题解:

  乍一看还是挺懵逼的。和HH去散步很像,思路也是类似的。

  复制一段我在HH去散步的题解里面写的一段话吧:

  考虑f[i][j]表示i和j是否右边相连,有为1,否则为0,那么f同时可以表示从每个点出发走一步到其他点的方案数。

  于是用一个和f长得一模一样的矩阵g来表示从每个点出发到其他点的方案数。

  那么考虑g如何转移。

  其实只要用g*f就可以表示一次转移了。

  为什么?

  设当前转移到了第t次,则g[i][j]表示i到j走t-1次的方案数(因为还没有更新)

  那么矩阵乘法做了什么?

  $g[i][j] = \sum_{l = 1}^{n}{g[i][l] * f[l][i]}$

  也就是它枚举了点i走了t - 1次到l,然后再从l走一次到j的方案数。

  是否能转移则要看l 到 j是否有边,而f[l][j]的意义刚好就是这样。

 

  但这题有一些特殊的限制,就是会有食人鱼周期性的游动。

  这样的话,转移就不再是一成不变的了,而是每天都有不同的不能去的地方,这样的话直接矩阵加速就做不了了。

  但是暴力做矩阵乘法还是对的,因为这里的矩阵乘法实际上就是做一次DP

  那么如何解决这个问题呢?

  观察到题目给的一个特殊性质:周期只有2,3,4三种。gcd(2, 3, 4) = 12.这意味着每过12天,每天可走的路线又会与12天前重复。

  所以如果我们把12天缩成1天,那么每天可走的路就是固定的了。

  因此我们预处理出这12天每天可以走的路线,然后暴力转移12次,把12天的矩阵都浓缩成1个,然后再做矩阵乘法即可。

  因为我们已经把12天缩成1天了,因此我们要乘的次数也变为了12分之一。

  然后剩下的不能被12整除的天数就在末尾暴力乘即可。

  由于矩阵乘法没有交换律,因此在暴力乘的过程中要格外注意顺序问题。

 

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define AC 52
  5 #define mod 10000
  6 
  7 int n, m, s, t, k, num;
  8 int g[AC][AC], go[AC][AC];//g记录连边情况,go表示第i条鱼,j时刻在哪
  9 
 10 struct matrix{
 11     int s[AC][AC];
 12 }ans, f, p[15], box;
 13 
 14 inline int read()
 15 {
 16     int x = 0;char c = getchar();
 17     while(c > '9' || c < '0') c = getchar();
 18     while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
 19     return x;
 20 }
 21 
 22 matrix cal(matrix x, matrix y)
 23 {
 24     for(R i = 1; i <= n; i ++)
 25         for(R j = 1; j <= n; j ++)
 26         {
 27             box.s[i][j] = 0;
 28             for(R l = 1; l <= n; l ++)
 29                 box.s[i][j] = (box.s[i][j] + x.s[i][l] * y.s[l][j]) % mod; 
 30         }
 31     return box;
 32 }
 33 
 34 inline void pre()
 35 {
 36     int a, b, T;
 37     n = read(), m = read(), s = read() + 1, t = read() + 1, k = read();
 38     for(R i = 1; i <= m; i ++)//读入桥
 39     {
 40         a = read() + 1, b = read() + 1;
 41         g[a][b] = g[b][a] = 1;
 42     }
 43     num = read();
 44     for(R i = 1; i <= num; i ++)//读入鱼
 45     {
 46         T = read();
 47         for(R j = 0; j < T; j ++) go[i][j] = read() + 1;
 48         for(R j = T; j < 12; j ++) go[i][j] = go[i][j - T];
 49     }
 50 }
 51 
 52 void qpow(int have)
 53 {
 54     if(have <= 0) return ;
 55     while(have)
 56     {
 57         if(have & 1) ans = cal(ans, f);
 58         f = cal(f, f);
 59         have >>= 1;
 60     }
 61 }
 62 
 63 void work()
 64 {
 65     for(R i = 0; i <= 11; i ++)//枚举时刻
 66     {
 67         for(R j = 1; j <= n; j ++)//枚举石墩
 68             for(R l = 1; l <= n; l ++) p[i].s[j][l] = g[j][l];
 69         for(R j = 1; j <= num; j ++)//枚举鱼
 70         {
 71             int x = go[j][i];//获取这条鱼在这个时刻在哪里
 72             for(R l = 1; l <= n; l ++) p[i].s[l][x] = 0;//枚举从哪个石墩来到x
 73         }
 74     }
 75     if(k <= 12)
 76     {
 77         ans = p[1];
 78         for(R i = 2; i <= min(k, 11); i ++) ans = cal(ans, p[i]);
 79         if(k == 12) ans = cal(ans, p[0]);
 80         printf("%d\n", ans.s[s][t]);
 81         return ;
 82     }
 83     ans = p[1];
 84     for(R i = 2; i <= 11; i ++) ans = cal(ans, p[i]);
 85     ans = cal(ans, p[0]);
 86     f = ans;
 87     qpow(k / 12 - 1);//因为有一个1在ans那 
 88     int b = k % 12;
 89     for(R i = 1; i <= b; i ++) ans = cal(ans, p[i]); 
 90     printf("%d\n", ans.s[s][t]);
 91 }
 92 
 93 int main()
 94 {
 95 //    freopen("in.in", "r", stdin);
 96     pre();
 97     work();
 98 //    fclose(stdin);
 99     return 0;
100 }
View Code

 

posted @ 2018-09-09 22:01  ww3113306  阅读(171)  评论(0编辑  收藏  举报
知识共享许可协议
本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。