前缀和推式子 + 尺取(双指针)

4395. 最大子矩阵 - AcWing题库

 在做这道题的时候,我陷入一个误区,那就是以为必须在矩阵c里面找最大值,但那样的话最好的时间复杂度是O(N^2*log(N^2)),大约8KW,肯定TLE

其实我们好好研究一下这个矩阵元素的组成

 我们其实可以发现,这个子矩阵[a1,b1]~[a2,b2]范围内数字的总和,就等于:

a[l] * (b[l] + b[l+1] + .... + b[r]) + 
a[l + 1] * (b[l] + b[l+1] + .... + b[r]) + 
....... +
a[r] * (b[l] + b[l+1] + .... + b[r])

= (a[l] + a[l+1] + .... + a[r]) * (b[l] + b[l+1] + .... + b[r])

= (sa[r] - sa[l - 1]) * (sb[r] - sb[l - 1]) = sa[l,r] * sb[l,r]

并且,这个区间包含的数字个数等于 = (ar - al + 1) * (br - bl + 1) = lena * lenb

#sa,sb表示前缀和,len表示长度

现在我们已经初步的把问题转化成了求在数组a里面找一个区间,在数组b里面找一个区间,使得这两个区间前缀和的乘积不大于x,并且这两个区间长度的乘积最大

那我们现在贪心的想,如果要满足上面的条件,我们肯定希望,在长度相同的时候,我们选择乘积最小的那个区间,这样就可以使另一个区间的长度更大。

那么,我们可以预处理一下ab数组在len=1,2,....,条件下的最小前缀和

然后,我们以此枚举a区间的长度,去找满足条件的b区间长度的最大值,注意,这里不需要用到双重循环,因为在上面我们预处理完成之后,假如对于b数组得到的新的数组lenb[i],表示长度为 i 的的最小区间,那么,lenb[i+1]>lenb[i]lenb数组是单调递增的!这一点可以由数据范围ai,bi>=1证明得到。

所以,在枚举a区间的长度的时候,我们可以二分查找符合条件的b区间,也可以使用尺取法(我感觉就是双指针算法)。

这里再解释一下尺取法:初始时由于我们枚举的是a的区间长度,所以我们让Lena = 1,同时让Lenb = m,让b等于最大的区间长度,之所以让b从最大区间长度倒着枚举,是因为随着a的区间长度的增加,b的区间长度是不可能增大的!

假如之前Lena=3、Lenb=2,那么当Lena=4时,Lenb<=2必然成立,因为对于Lena=3时,Lenb都取不到3,现在Lena增大,又由上面推导,区间长度越大,值就越大,所以b的长度必然减小,或者不变。

因此,我们让就可以让Lenb初始时等于最大长度m,整个过程中Lenb只会减小,不会增加,也就是只会向左走。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2010, INF = 1e9;

int n, m, x;
int a[N], b[N], s1[N], s2[N];

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i ++ )
    {
        cin >> s1[i];
        s1[i] += s1[i - 1];
    }
    for(int i = 1; i <= m; i ++ )
    {
        cin >> s2[i];
        s2[i] += s2[i - 1];
    }
    
    for(int len = 1; len <= n; len ++ )
    {
        a[len] = INF;
        for(int l = 1; l + len - 1 <= n; l ++ )
        {
            int r = l + len - 1;
            a[len] = min(a[len], s1[r] - s1[l - 1]);
        }
    }
    

    for(int len = 1; len <= m; len ++ )
    {
        b[len] = INF;
        for(int l = 1; l + len - 1 <= m; l ++ )
        {
            int r = l + len - 1;
            b[len] = min(b[len], s2[r] - s2[l - 1]);
        }
    }
    
    // for(int i = 1; i <= n; i ++ )   cout << a[i] << " ";
    // cout << endl;
    // for(int j = 1; j <= m; j ++ )   cout << b[j] << " ";
    // cout << endl;
    
    cin >> x;
    
    int res = -1;
    for(int i = 1, j = m; i <= n; i ++ )
    {
        while(j && a[i] > x / b[j]) j -- ;
        res = max(res, i * j);
    }
    
    cout << res << endl;
    
    return 0;
}

posted @ 2022-05-05 08:41  光風霽月  阅读(18)  评论(0编辑  收藏  举报