Sub Matrix Sum 含负数的最短区间+ 矩阵一维化

Sub Matrix Sum 含负数的最短区间+ 矩阵一维化

小记

  1. 训练赛补题,该题是 2019 UCF Practice F题,也是2013 UCF的题目

  2. 先看的是广外大给的题解,采用的是尺取法,但实际上含有负数的情况是不能用它的,于是我理解了它的代码之后却发现是wa的,不过也对尺取法有了更深的理解了。接下来看到是南京信息工程大学的题解,采用的是二分法。

  3. 本题学到的(1)矩阵一维化 (2)尺取法 (3)二分法

  4. 本篇文章的想法是从这道题目里,提取出一个问题模型,即对于无负数情况下时求最短区间和大于m 的问题可以用尺取法,复杂度是O(n)

    而对于有负数情况下,用本题题解的的二分法,复杂度是O(n*log n )

题意

有一个R*C大小的矩阵,元素为整数,可正可负,求最小子矩阵的大小,该子矩阵要满足矩阵和大于M。

R, C (1 ≤ R ≤ 100,000; 1 ≤ C ≤ 100,000; 1 ≤ R*C ≤ 100,000)

分析
  1. 由R,C的数据范围可以知,矩阵只能开成1维的。

  2. 先枚举每个高度的不同高度的子矩阵,把子矩阵的每一列变成一个元素,那么就变成了长度为C的一维数组。

    比如 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 \begin{matrix} 1 & 2 & 3 & 4\\ 5 & 6 & 7 & 8\\ 9 & 10 & 11 & 12\\ 13 & 14 &15 & 16 \end{matrix} 15913261014371115481216 取h=2,r=1时,求是取 5 6 7 8 9 10 11 12 \begin{matrix} 5 & 6 & 7 & 8\\ 9 & 10 & 11 & 12 \end{matrix} 59610711812 这个个子矩阵,以第1行为顶,高度为2,

    合成一维数组就是 14 16 18 20

  3. 问题变成了长度为C的一维数组求大于M的最短区间长度

    那么对于有负数的该如何求呢?

    对每一列枚举,sum表示到此时的和。 minus_sum里存储的是 − ∑ k = 1 k = i a [ k ] -\sum_{k=1}^{k=i}a[k] k=1k=ia[k] ,它们排序的依据其实是 (当前列-它们代表的列)从大到小。

    int ptr=0;
    int col[MAXCOL];
    int minus_sum[MAXCOL]
    ll sum=0;
    
    for(int i=1;i<=C;++i)		//对每一列枚举
    {
    	sum+=a[i];
    	int delta=sum-a[i];
    	//假如要减去一个相同的值,扣去更少的列不如扣去更多的列。
    	while(ptr>0&&minus_sum [ptr]<delta)--ptr;
    	
        col[++ptr]=i;minus_sum[ptr]=delta; 
        
    	int lef=1,rig=i,mid;
    	while(rig-lef>1)
    	{
    		mid=(lef+rig)/2;
    		if(minus_sum[mid]+sum >=M)lef=mid;  //符合条件
    		else rig=mid;
    	}
        if(minus_sum[rig]<M)--rig;
    	ans=min(ans,(i-col[rig]));
    }
    

    看到这里,这题的思路就应该大概明白了,可以直接看文末的题解代码了,而这里还想要写一下关于广外大给的尺取法不能用的原因

     //这是广外给的处理这里的一维数组的代码
     /*我觉得会wa的原因是虽然它考虑了第一个是小于等于0的情况,求直接left+1,但是有时当前right还没取到和它最好的left,就右移了,比如m=4时,数组为2 -5 6 4,会达到这个取了2 -5 6这个状态,那么这时还会向右移动,而6没有取到雨它最好的left,却没有机会了。*/
     for(k=1;k<=C;++k) array[k] = sumMatrixRef(j,k)-sumMatrixRef(i,k);
                LL sum = 0;
                int left = 1;
                int right = 1;
                while(right<=C){
                    if(left==right){sum = sum+array[right];++right;}           
                    else if(array[left]<=0){sum = sum-array[left];++left;}
                    else if(sum>=S){sum = sum-array[left];++left;}
                    else{sum = sum+array[right];++right;}
                    if(left<right&&sum>=S){
                        if (res>((right-left)*(j-i)))
                            res=((right-left)*(j-i));
                    }
                }
                while(left<=C){
                    if(sum>=S){
                        if (res>((right-left)*(j-i)))
                            res=((right-left)*(j-i));
                    }
                    sum = sum-array[left];
                    ++left;
                }
    
原题和代码

原题

You have written many programs to search mazes so matrix search shouldn’t be any different, or will it?

The Problem:

An integer matrix with R rows and C columns has C(R,2)*C(C,2) sub matrices. We want to select a sub matrix with sum (the sum of all integers in it) greater than or equal to a given integer S. We want the size of the sub matrix to be the least possible. The size of a sub matrix is defined as the number of elements in that sub matrix (i.e., number of rows * number of columns in that sub matrix).

The Input:

The first input line contains a positive integer, t, indicating the number of test cases. The first line of each test case consists of three integers R, C (1 ≤ R ≤ 100,000; 1 ≤ C ≤ 100,000; 1 ≤ R*C ≤ 100,000) and S. Next R lines contain the description of the matrix. Each of these R lines contains C integers separated by a single space. All integers (other than R and C) are between -109 and +109, inclusive.

The Output:

For each test case, output the size of the minimum sub matrix whose sum is greater or equal to the given S. If there is no such sub matrix, output -1.

//参照2份题解的代码改的
#include "bits/stdc++.h"
using namespace  std;
typedef long long ll ;
const int MAXN=1E6+100;  //注意在本代码中开1e5+100是不够的,因为 matrix[0]~matrix[C]之间是个缓冲区
int matrix[MAXN],R,C,M;
ll colsum[MAXN],slices[MAXN];
inline int & InMatrix(int r,int c){ return matrix[r*C+c];}
inline ll& InColsum(int r,int c){return colsum[r*C+c] ;}
void inputMatrix()
{
    //因为时间复杂度为 R*R*(C+C*lg(C) ),因此要使得R<C
    if(R<=C)
    {
        for(int i=1;i<=R;++i)
            for(int in,d=1;d<=C;++d){
                scanf("%d",&in);
                InMatrix(i,d)=in;
            }
    }
    else {
        swap(R,C);
        for(int i=1;i<=C;++i)
            for(int in,d=1;d<=R;++d){
                scanf("%d",&in);
                InMatrix(d,i)=in;
            }
    }
}
int how_many_col[MAXN];
ll minus_sum[MAXN];
int main()
{
//    freopen("in.text","r",stdin);
    int cas;
    scanf("%d",&cas);
    while (cas--)
    {
        scanf("%d %d %d",&R,&C,&M);
        inputMatrix();

        memset(colsum,0,sizeof(colsum) );
        for(int r=1;r<=R;++r)
            for(int c=1;c<=C;++c)
                InColsum(r,c)=InColsum(r-1,c)+InMatrix(r,c);

        int ans=R*C+10;
        for(int h=1;h<=R;++h)
            for(int r=0;r+h<=R;++r)
            {
                if(h>=ans)break;
                for(int c=1;c<=C;++c)slices[c]=InColsum(r+h,c)-InColsum(r,c);
                ll sum=0;
                int ptr=0;
                for(int c=1;c<=C;++c)
                {
                    sum+=slices[c];
                    ll delta=slices[c]-sum;
                    while (ptr>0&& minus_sum[ptr]<=delta)--ptr; //要减去一个值,扣去更少的行不如扣去更多的行。

                    minus_sum[++ptr]=delta;
                    how_many_col[ptr]=c;

                    if(minus_sum[1]+sum<M)continue;    //如果一列都不除去都不能得到M,那就不必二分了。

                    int lef=1,rig=ptr,mid;
                    while (rig-1>lef)
                    {
                        mid=(lef+rig)/2;
                        if( minus_sum[mid]+sum>=M )lef=mid;
                        else rig=mid;
                    }
                    if(minus_sum[rig]+sum<M )--rig;
                    ans=min(ans,h*(c-how_many_col[rig]+1 ) );
                }

            }
        if(ans>R*C)ans=-1;
        printf("%d\n",ans);
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是Mally呀!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值