蓝桥杯2022年第十三届省赛真题-统计子矩阵(超详细解析)

题目描述

给定一个 N × M 的矩阵 A,请你统计有多少个子矩阵 (最小 1 × 1,最大 N × M) 满足子矩阵中所有数的和不超过给定的整数 K?

输入格式

第一行包含三个整数 N, M 和 K.

之后 N 行每行包含 M 个整数,代表矩阵 A.

输出格式

一个整数代表答案。

样例输入

3 4 10

1 2 3 4

5 6 7 8

9 10 11 12

样例输出

19

提示

满足条件的子矩阵一共有 19,包含:

大小为 1 × 1 的有 10 个。

大小为 1 × 2 的有 3 个。

大小为 1 × 3 的有 2 个。

大小为 1 × 4 的有 1 个。

大小为 2 × 1 的有 3 个。

对于 30% 的数据,N, M ≤ 20. 对于 70% 的数据,N, M ≤ 100.

对于 100% 的数据,1 ≤ N, M ≤ 500; 0 ≤ Ai j ≤ 1000; 1 ≤ K ≤ 250000000.

//首先,注意子矩阵是输入进数据的一部分
//输入进的数据位置是不能变的,是在这个固定的矩阵中选取其中小的矩阵!
//而不是输入进入数据然后随机排列!

//本文以示例数据作为参考
#include<stdio.h>
#include<stdlib.h>
int s[505][505];
int main()
{
    int n,m,k;
     scanf("%d%d%d",&n,&m,&k);
     for(int i=1;i<=n;i++)
     {
         for(int j=1;j<=m;j++)
         {
             scanf("%d",&s[i][j]);
             s[i][j]+=s[i-1][j];
             //双循环插入数据,
             //加上前面的是因为相当于选取上下的矩阵使用,
       //每一次都加上前面行数据的总和.
       //每一个对应行数的数组数据都是当前行加前面所有行数的总和
       //前缀和的思想


       //这么写的好处就是不会超时,因为选中想要的数据都可以通过加减的运算直接得出
       //我就不需要自己开n个循环自己去截取,这样会显得很麻烦而且必定超时

         }
     }         
     
     long long int res=0;//长整型防止数据过大溢出
     //long long 是其简写;
   
   
     for(int i=1;i<=n;i++)//选定截断的子矩阵范围
    
     {
         for(int j=i;j<=n;j++)//选定所需的子矩阵范围
         //条件注意看清楚不要乱写!
         
         //两个循环就相当于,先选定第一行开始选取行数,然后跟同时选第一二,第一二三,.....行一起选择
//(因为前面的数组每次都把前面数组的数据都加了起来,所以每次下一行的数组的数据都是前面
   //行对应列的数据之和)
    //然后再从第二行开始,跟着同时选二三,二三四,.....行一起
        
         
       {
           for(int left=1,right=1,sum=0;right<=m;right++)//是<=!
       //每一轮sum都会重置一次,sum的范围只在单次选定的行数中

       //right++既是推进遍历,又是一种默认,只要这层left不增
       //就相当于这个数据可行,所以就要加1,而如果left增加
       //说明当前sum太大,我就要舍弃最左边的数,要看当前数有多大,
       //来决定减少最左边数据的多少,即left增多少
       
       
           //right记录增加,left记录舍去,sum作为判断是否能同时选取多少个数,res为最终多少个矩阵
           //原理就是选取和舍去,如从1开始,满足条件,记为1,然后加上2同样不超过,所以继续记录,此时res等于三是因为,这两个数据现在是同时可以选中,因此分别
           //为单独的1和2,以及1和2一起成为一个矩阵的三种情况,后面的3,4也是选取不同子矩阵成为十种。
       //举例如1,2,3中可以有1,2/2,3/1,2,3三种(注意要连续才为子矩阵)
       
           //而从第二行开始就是同时选取1和5这两个数据和后面的2、6尝试连结,没有超过k就选取新的子矩阵,超过就删去1、5,剩下2、6,继续往下连结,重复操作
           //后面第三行就是1,5,9连在一起尝试连结,以此类推
           //而后面从第二行单独数据开始时后面同样重复,只是不再选取上一行的数据,从而达到全面查找矩阵的目的,如5,9绑在一起,参考图片。
           {
                   sum+=s[j][right]-s[i-1][right];
           //选取子矩阵大小
                              while(sum>k)
                              {
                                  sum-=s[j][left]-s[i-1][left];//这里是去掉最左边无用的数据
                                  left++;
                    //减少的个数增加
                                  
                              }
//                              res+=right-left+1;可读性差
                              res+=right-(left-1);
                              //这里的加1其实是因为下标问题,因为要用到left作下标,0又没有存放数据只有1开始才有
                        //因此不得不给left赋值上1,如果从0开始放数据,其实是res+=right-left;
                    
                    //而更清楚的展示从1开始应该是res+=right - (left - 1);才更准确
          //(开始的时候不存在舍去的数据)
                    //至于为什么就是找规律

                    //一开始从1,2,3,4
                    //得到的结果就有1,3,6,10,显然就是加下一个n即n+1的结果
                    
                    //left是不可能会比right大的
                           //因为right就相当于在右边找数据,left相当于判断是否应该删除
                           //如果sum>k,那么left就会选中在sum中的最左边的数据删除
                           //一直到sum<=k为止,再退出while增加该情况.
                           //最坏的情况也是当前right增加的这个结果,一旦这个结果也被删除
                           //那么sum=0,因此最差也是res+=0,不可能会出现倒减
                           //而删除就是减少选取新的子矩阵的情况,意思就是有数据不能在这个排列中了
                           //要删去,根据子矩阵的选取,也就是删除最左边的,也只能舍去最左边的
                           //(子矩阵需要不间断)
                    
//总结:    left和right相当于划定边界,在这之间范围内的数可以选取的子矩阵。
//    right一直往前走left紧跟着。只要sum大于k,left也往右移缩小范围,left和right都是两用,下标和划定数的数量范围
//right一直都是往前走的,left也有可能会删除,所以确保了每次选取的数都是新的,因此每一轮都要加上right-(left-1);
//          
          
                
           }
           
               
           }
           
       
        

     }
     printf("%lld",res);
     
     
                     
    
    
    return 0;
}

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值