前缀和推式子 + 尺取(双指针)
在做这道题的时候,我陷入一个误区,那就是以为必须在矩阵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,并且这两个区间长度的乘积最大
那我们现在贪心的想,如果要满足上面的条件,我们肯定希望,在长度相同的时候,我们选择乘积最小的那个区间,这样就可以使另一个区间的长度更大。
那么,我们可以预处理一下a和b数组在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;
}