Sub Matrix Sum 含负数的最短区间+ 矩阵一维化
小记
-
训练赛补题,该题是 2019 UCF Practice F题,也是2013 UCF的题目
-
先看的是广外大给的题解,采用的是尺取法,但实际上含有负数的情况是不能用它的,于是我理解了它的代码之后却发现是wa的,不过也对尺取法有了更深的理解了。接下来看到是南京信息工程大学的题解,采用的是二分法。
-
本题学到的(1)矩阵一维化 (2)尺取法 (3)二分法
-
本篇文章的想法是从这道题目里,提取出一个问题模型,即对于无负数情况下时求最短区间和大于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)
分析
-
由R,C的数据范围可以知,矩阵只能开成1维的。
-
先枚举每个高度的不同高度的子矩阵,把子矩阵的每一列变成一个元素,那么就变成了长度为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
-
问题变成了长度为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);
}
}