最大子矩阵

最大子矩阵

给定一个长度为 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

数据范围

前三个测试点满足 1n,m5
所有测试点满足 1n,m20001ai,bi20001x2×109

输入样例1:

3 3
1 2 3
1 2 3
9

输出样例1:

4

输入样例2:

5 1
5 4 2 4 5
2
5

输出样例2:

1

 

解题思路

  一开始推导出了求子矩阵和的公式,就是s=(ak1+ak2+akn)×(br1+br2+brm)。先预处理出数组a各个长度的区间所对应的区间和,然后让所有求得的和都记录一个对应的最大区间长度,同时对得到的和进行离散化。然后通过枚举数组b各个长度的区间所对应的区间和s,在离散化数组中二分找到小于等于xs的值,通过映射得到这个和在数组a中对应的最大区间长度,然后与数组b的区间长度进行乘积,将这个结果与答案取一个最大值。但这种做法一开始没过,也不知道错哪里,后面改了下又TLE了。

  首先如果有一个子矩阵,对应的行从k1kn,列从r1rm,那么子矩阵中第一行的和为ak1×(br1++brm),同理,一直到第n行的和为akn×(br1++brm),将每一行的和加起来就得到子矩阵的和,提取公因式(br1++brm),最后会得到s=(ak1+akn)×(br1+brm)

  问题就是任选一个a的一个区间,任选一个b的区间,使得两个区间的和的乘积要小于等于x,并且两个区间长度的乘积最大。如果直接枚举的话时间复杂度是O(n4)

  我们考虑一下,对于a中长度都为len的区间,我们是要在b中找到一个尽可能长的区间,使得两个区间的和的乘积小于等于x。因为每一个数都是正数,所以区间长度越长,区间和越大。如果有a的区间和sab的区间和sb,那么在满足sa×sbx的情况下,要让b的区间长度越大,意味着sb越大,因此要让sa越小。因此对于a中长度都为len的区间,应该选择区间和最小的那个。即长度一定的时候选择区间和最小的那个。这样就从n2的自由度降到n。数组b同理,自由度可以降到m

  sa[i]表示数组a中长度为i的最小区间和。可以发现sa[i+1]>sa[i]。假设有sa[i+1]sa[i],因为每一个元素都是正数,因此我们可以从长度为i+1的区间中删除一个数,长度变为i,此时的和变成s,也必然满足s<sa[i+1]sa[i],即长度为i的区间存在一个比sa[i]更小的区间和,这就与sa[i]表示数组a中长度为i的最小区间和矛盾了。同理sb[i]

  所以我们可以枚举i,当确定了sa[i]后,找到一个最大的j,使得满足sa[i]×sb[j]x。这里可以用双指针或二分来做。

  双指针的话,当i单调往后走,j一定是单调往前走的。

  AC代码如下:

  双指针:

复制代码
 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int N = 2010;
 6 
 7 int sa[N], sb[N];
 8 int a[N], b[N];
 9 
10 int main() {
11     int n, m, x;
12     scanf("%d %d", &n, &m);
13     for (int i = 1; i <= n; i++) {
14         int val;
15         scanf("%d", &val);
16         sa[i] = sa[i - 1] + val;
17     }
18     for (int i = 1; i <= m; i++) {
19         int val;
20         scanf("%d", &val);
21         sb[i] = sb[i - 1] + val;
22     }
23     scanf("%d", &x);
24     
25     for (int len = 1; len <= n; len++) {
26         a[len] = 2e9;
27         for (int i = 1; i + len - 1 <= n; i++) {
28             int j = i + len - 1;
29             a[len] = min(a[len], sa[j] - sa[i - 1]);
30         }
31     }
32     
33     for (int len = 1; len <= m; len++) {
34         b[len] = 2e9;
35         for (int i = 1; i + len - 1 <= m; i++) {
36             int j = i + len - 1;
37             b[len] = min(b[len], sb[j] - sb[i - 1]);
38         }
39     }
40     
41     int ret = 0;
42     for (int i = 1, j = m; i <= n; i++) {
43         while (j && a[i] > x / b[j]) {
44             j--;
45         }
46         ret = max(ret, i * j);
47     }
48     
49     printf("%d", ret);
50     
51     return 0;
52 }
复制代码

  二分:

复制代码
 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int N = 2010;
 6 
 7 int sa[N], sb[N];
 8 int a[N], b[N];
 9 
10 int find(int l, int r, int val) {
11     while (l < r) {
12         int mid = l + r + 1 >> 1;
13         if (b[mid] <= val) l = mid;
14         else r = mid - 1;
15     }
16     
17     return b[l] <= val ? l : 0;
18 }
19 
20 int main() {
21     int n, m, x;
22     scanf("%d %d", &n, &m);
23     for (int i = 1; i <= n; i++) {
24         int val;
25         scanf("%d", &val);
26         sa[i] = sa[i - 1] + val;
27     }
28     for (int i = 1; i <= m; i++) {
29         int val;
30         scanf("%d", &val);
31         sb[i] = sb[i - 1] + val;
32     }
33     scanf("%d", &x);
34     
35     for (int len = 1; len <= n; len++) {
36         a[len] = 2e9;
37         for (int i = 1; i + len - 1 <= n; i++) {
38             int j = i + len - 1;
39             a[len] = min(a[len], sa[j] - sa[i - 1]);
40         }
41     }
42     
43     for (int len = 1; len <= m; len++) {
44         b[len] = 2e9;
45         for (int i = 1; i + len - 1 <= m; i++) {
46             int j = i + len - 1;
47             b[len] = min(b[len], sb[j] - sb[i - 1]);
48         }
49     }
50     
51     int ret = 0;
52     for (int i = 1; i <= n; i++) {
53         ret = max(ret, i * find(1, m, x / a[i]));
54     }
55     
56     printf("%d", ret);
57     
58     return 0;
59 }
复制代码

 

参考资料

  AcWing 4395. 最大子矩阵(AcWing杯 - 周赛):https://www.acwing.com/video/3764/

posted @   onlyblues  阅读(162)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示