1. 问头描述:
给定一个长度为 n 的整数数组 a1,a2,…,an 和一个长度为 m 的整数数组 b1,b2,…,bm;设 c 是一个 n × m 的矩阵,其中 ci,j = ai × bj,请你找到矩阵 c 的一个子矩阵,要求:该子矩阵所包含的所有元素之和不超过 x,并且其面积(包含元素的数量)应尽可能大,输出满足条件的子矩阵的最大可能面积(即包含元素的最大可能数量)。
输入格式
第一行包含两个整数 n,m。第二行包含 n 个整数 a1,a2,…,an。第三行包含 m 个整数 b1,b2,…,bm。第四行包含一个整数 x;
输出格式
一个整数,表示满足条件的子矩阵的最大可能面积(即包含元素的最大可能数量)。如果不存在满足条件的子矩阵,则输出 0。
数据范围
前三个测试点满足 1 ≤ n,m ≤ 5。
所有测试点满足 1 ≤ n,m ≤ 2000,1 ≤ ai,bi ≤ 2000,1 ≤ x ≤ 2 × 10 ^ 9;
输入样例1:
3 3
1 2 3
1 2 3
9
输出样例1:
4
输入样例2:
5 1
5 4 2 4 5
2
5
输出样例2:
1
来源:https://www.acwing.com/problem/content/description/4398/
2. 思路分析:
因为都是正数,所以总和具有一定的单调性,因为 n,m 最大为 2000,所以需要将时间复杂度控制到 O(n ^ 2) 以内,首先需要找一下规律,如果我们确定了某个子矩阵那么如何计算子矩阵的和呢?我们可以画一下图,根据下面的图可以知道当我们确定了一个子矩阵之后那么子矩阵的和为 a 中行的区间和乘以 b 中列的区间和,如何枚举 a 和 b 的区间呢?a 中任选一个区间,b 中任选一个区间使得乘积之和小于等于 x 然后更新一下答案,但是如果直接枚举 a 和 b 的所有区间那么时间复杂度为 O(n ^ 4),肯定会超时,所以我们需要考虑一下如何优化,如果当前 a 中存在两个长度相等的区间,由于需要使得总和小于等于 x 并且使得子矩阵的元素尽可能多,所以我们需要在两个长度相等的区间中选择一个区间和最小的区间,这样可以使得 b 选择的区间尽可能长,所以对于 b 中的区间也是一样的,当区间长度相等的时候我们需要选择区间和最小的区间,基于这个想法我们可以预处理出 a,b 中区间长度为 1~len 的最小的区间和;并且可以发现对于 a 或 b 中的区间和是具有单调性的,例如 a 中长度较大的区间和一定大于长度较小的区间和,也即长度越大那么区间和是越大的,所以我们可以使用二分或者双指针来解决,下面使用双指针来解决,定义两个指针 i,j,指针 i 往右走的时候,指针 j 一定单调往左走,当满足子矩阵的区间和小于等于 x 的时候更新一下答案即可。
3. 代码如下:
python:
class Solution:
def process(self):
n, m = map(int, input().split())
a = [0] + list(map(int, input().split()))
b = [0] + list(map(int, input().split()))
s1, s2 = [0], [0]
INF = 10 ** 10
# 计算前缀和
for i in range(1, n + 1):
s1.append(s1[i - 1] + a[i])
for i in range(1, m + 1):
s2.append(s2[i - 1] + b[i])
for l in range(1, n + 1):
# 一开始的时候当前长度为l的区间和为INF
a[l] = INF
i = 1
while i + l - 1 <= n:
j = i + l - 1
# 枚举所有长度为l的区间求解长度为l的最小区间和
a[l] = min(a[l], s1[j] - s1[i - 1])
i += 1
for l in range(1, m + 1):
b[l] = INF
i = 1
while i + l - 1 <= m:
j = i + l - 1
b[l] = min(b[l], s2[j] - s2[i - 1])
i += 1
res = 0
x = int(input())
j = m
# 双指针, i 往右走 j 肯定往左走
for i in range(1, n + 1):
while j and b[j] > x // a[i]: j -= 1
# 说明当前满足 a[i] * b[j] <= x 那么更新一下答案
res = max(res, i * j)
print(res)
if __name__ == '__main__':
Solution().process()
go:
package main
import (
"bufio"
"fmt"
"io"
"os"
)
// 求解a, b 的最小值
func min(a, b int) int {
if a < b {
return a
}
return b
}
// 求解a, b 的最大值
func max(a, b int) int {
if a > b {
return a
}
return b
}
func run(r io.Reader, w io.Writer) {
in := bufio.NewReader(r)
out := bufio.NewWriter(w)
defer out.Flush()
var (
n, m int
)
fmt.Fscan(in, &n, &m)
a := make([]int, n+10)
b := make([]int, m+10)
s1 := make([]int, n+10)
s2 := make([]int, m+10)
for i := 1; i <= n; i++ {
fmt.Fscan(in, &a[i])
s1[i] += s1[i-1] + a[i]
}
for i := 1; i <= m; i++ {
fmt.Fscan(in, &b[i])
s2[i] += s2[i-1] + b[i]
}
INF := 10000000000
// 求解所有长度为len的最小值
for len := 1; len <= n; len++ {
a[len] = INF
for i := 1; i+len-1 <= n; i++ {
j := i + len - 1
a[len] = min(a[len], s1[j]-s1[i-1])
}
}
for len := 1; len <= m; len++ {
b[len] = INF
for i := 1; i+len-1 <= m; i++ {
j := i + len - 1
b[len] = min(b[len], s2[j]-s2[i-1])
}
}
var (
x, res int
)
fmt.Fscan(in, &x)
j := m
// 双指针
for i := 1; i <= n; i++ {
// 为了防止溢出所以需要将乘法变为除法
for j > 0 && a[i] > x/b[j] {
j -= 1
}
res = max(res, i*j)
}
fmt.Fprint(out, res)
}
func main() {
run(os.Stdin, os.Stdout)
}