HLOJ1361 Walking on the Grid II 矩阵快速幂
题目分析:
就当是一次记录吧,2013年绍兴市市赛的一题,重现赛当时我想递推可能是矩阵快速幂吧,但是这个递推公式真没推出来(赛后猛如虎系列),这题和第一题有联系又有区别,第一题的递推很简单,dp[i][j] = dp[i-1][j] + dp[i][j-1],但是第二题有两个问题:1.H的范围扩大到1000000000,二维数组无法存储(空间过大)2.普通递推的话递推的次数太多(w*h,时间过大),简单来说必须改变一下思维,否则一定爆内存且超时,之前的递推公式也用不了了
这里先放一张我的手稿(丑图警告)
这里需要通过仔细的观察,将上图处于同一斜线的位置归为同一层(或者说这些在同一斜线上的点的横坐标与纵坐标的和是相同的),还记得上面提到的两个难题吗,现在来解决空间过大无法存储二维数组的问题,此时我们观察,对于图中的某一点(i,j)来说,到达它的行走方案一定是到达(i-1,j)和到达(i,j-1)的方案的和,就斜线上每个位置来说,就等于它左边的与上面的格子的数的和,而且这里每条斜线上的点仅仅用于下一斜线上的点的计算(这里就很像背包问题的二维化一维的思想)我们可以将二维数组转化成一维数组,dp[j]代表第i条斜线的从右上往左下的第j个点的可达方案数,由于w<=30,所以斜线的长度最长也只有31(有一个0点需要辅助计算)
此时空间的问题就解决了,我们接下来需要做的就是通过条斜线上的点点,推出下一条斜线上的点的种数,且我们很容易发现(就斜线上某一个点来说,dp[j] = dp[j] + dp[j-1],这里要注意,此时dp[j]代表的是第i条斜线的从右上往左下第j个点,而此时只有一个一维数组在存放,所以前面的等式,前一个dp[j]代表的是第i条斜线的dp[j]的值,而等式后面的dp[j]与dp[j-1]代表的是第i-1条斜线的这两个点的走法),此时我们将第一条斜线的数据抽象成一个1XW的矩阵,它乘上一个WXW的矩阵就会得到一个代表第二条斜线的矩阵
且根据我们的推理,我们的递推公式是dp[j] = dp[j] + dp[j-1],最后一条斜线所抽象的第w个点就是到达W*H的位置所有的行走方案的总数,而对于出现的障碍点,我们可以对它们根据横纵坐标之和进行排序(横纵之和就是斜线的编号,初始点1,1所以斜线编号我定义为2),然后对于当前的斜线求和下一个障碍点所在的斜线编号值差,矩阵快速幂求出当前障碍点的所有dp[j]的种数,而对于障碍点,dp[j] = 0,这样从下一条斜线开始就会受到影响,对于其他的障碍点也是相同的处理,如果同一条斜线有多个障碍点就将dp[j]都 = 0,此时的j就是障碍点的横坐标,最后求一次到达第(w+h)条斜线的举证快速幂
代码:
1 #include<iostream> 2 #include<algorithm> 3 #include<cmath> 4 using namespace std; 5 6 int w, h, d; 7 const long long mod = 1e9 + 7; 8 struct mat{ 9 long long m[35][35]; 10 }; 11 struct Node{ 12 long long x, y; 13 long long sum; 14 }danger[55]; 15 16 bool cmp(Node a, Node b){ 17 return a.sum < b.sum; 18 } 19 20 mat operator * (mat a, mat b){ 21 mat ret; 22 for(int i = 0; i <= w; i++){ 23 for(int j = 0; j <= w; j++){ 24 long long temp = 0; 25 for(int k = 0; k <= w; k++){ 26 if(a.m[i][k] && b.m[k][j]){ 27 temp += a.m[i][k] * b.m[k][j]; 28 temp %= mod; 29 } 30 } 31 ret.m[i][j] = temp; 32 } 33 } 34 return ret; 35 } 36 37 mat pow_mat(mat res, mat a, int n){ 38 while(n){ 39 if(n&1) res = res * a; 40 a = a * a; 41 n >>= 1; 42 } 43 return res; 44 } 45 46 int main(){ 47 while(scanf("%d%d%d", &w, &h, &d) != EOF){ 48 for(int i = 0; i < d; i++){ 49 scanf("%d%d", &danger[i].x, &danger[i].y); 50 danger[i].sum = danger[i].x + danger[i].y; 51 } 52 sort(danger, danger+d, cmp); 53 mat x; //构造一个幂矩阵 54 for(int i = 0; i <= w; i++){ 55 for(int j = 0; j <= w; j++){ 56 x.m[i][j] = 0; 57 } 58 } 59 for(int i = 0; i <= w-1; i++){ 60 for(int j = i; j < i+2; j++){ 61 x.m[i][j] = 1; 62 } 63 } 64 x.m[w][w] = 1; 65 mat res; 66 for(int i = 0; i <= w; i++){ 67 for(int j = 0; j <= w; j++){ 68 res.m[i][j] = 0; 69 } 70 } 71 res.m[0][1] = 1; //默认横纵坐标和为2开始,即(1,1)点 72 int cen = 2; //这里定义的层数为点的横纵坐标之和 73 for(int i = 0; i < d; i++){ 74 if(danger[i].sum > cen){ //当前的危险点层数大于已经完成计算的层数 75 res = pow_mat(res, x, danger[i].sum - cen); 76 cen = danger[i].sum; 77 res.m[0][danger[i].x] = 0; 78 }else if(danger[i].sum == cen){ //当前的危险点的层数等于已经完成的计算的层数则直接更新危险点为0种 79 res.m[0][danger[i].x] = 0; 80 } 81 } 82 if(h+w > cen) res = pow_mat(res, x, h+w - cen); //最后将剩余的层数直接求完 83 printf("%d\n", res.m[0][w]); 84 } 85 return 0; 86 }