最大子矩阵
最大子矩阵
给定一个长度为 的整数数组 和一个长度为 的整数数组 。
设 是一个 的矩阵,其中 。
请你找到矩阵 的一个子矩阵,要求:该子矩阵所包含的所有元素之和不超过 ,并且其面积(包含元素的数量)应尽可能大。
输出满足条件的子矩阵的最大可能面积(即包含元素的最大可能数量)。
输入格式
第一行包含两个整数 。
第二行包含 个整数 。
第三行包含 个整数 。
第四行包含一个整数 。
输出格式
一个整数,表示满足条件的子矩阵的最大可能面积(即包含元素的最大可能数量)。
如果不存在满足条件的子矩阵,则输出 。
数据范围
前三个测试点满足 。
所有测试点满足 ,,。
输入样例1:
3 3 1 2 3 1 2 3 9
输出样例1:
4
输入样例2:
5 1 5 4 2 4 5 2 5
输出样例2:
1
解题思路
一开始推导出了求子矩阵和的公式,就是。先预处理出数组各个长度的区间所对应的区间和,然后让所有求得的和都记录一个对应的最大区间长度,同时对得到的和进行离散化。然后通过枚举数组各个长度的区间所对应的区间和,在离散化数组中二分找到小于等于的值,通过映射得到这个和在数组中对应的最大区间长度,然后与数组的区间长度进行乘积,将这个结果与答案取一个最大值。但这种做法一开始没过,也不知道错哪里,后面改了下又了。
首先如果有一个子矩阵,对应的行从到,列从到,那么子矩阵中第一行的和为,同理,一直到第行的和为,将每一行的和加起来就得到子矩阵的和,提取公因式,最后会得到。
问题就是任选一个的一个区间,任选一个的区间,使得两个区间的和的乘积要小于等于,并且两个区间长度的乘积最大。如果直接枚举的话时间复杂度是。
我们考虑一下,对于中长度都为的区间,我们是要在中找到一个尽可能长的区间,使得两个区间的和的乘积小于等于。因为每一个数都是正数,所以区间长度越长,区间和越大。如果有的区间和,的区间和,那么在满足的情况下,要让的区间长度越大,意味着越大,因此要让越小。因此对于中长度都为的区间,应该选择区间和最小的那个。即长度一定的时候选择区间和最小的那个。这样就从的自由度降到。数组同理,自由度可以降到。
表示数组中长度为的最小区间和。可以发现。假设有,因为每一个元素都是正数,因此我们可以从长度为的区间中删除一个数,长度变为,此时的和变成,也必然满足,即长度为的区间存在一个比更小的区间和,这就与表示数组中长度为的最小区间和矛盾了。同理。
所以我们可以枚举,当确定了后,找到一个最大的,使得满足。这里可以用双指针或二分来做。
双指针的话,当单调往后走,一定是单调往前走的。
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/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16095332.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效