最大子矩阵
作者:互联网
最大子矩阵
给定一个长度为 $n$ 的整数数组 $a_{1},a_{2}, \dots ,a_{n}$ 和一个长度为 $m$ 的整数数组 $b_{1},b_{2}, \dots ,b_{m}$。
设 $c$ 是一个 $n \times m$ 的矩阵,其中 $c_{i,j} = a_{i} \times b_{j}$。
请你找到矩阵 $c$ 的一个子矩阵,要求:该子矩阵所包含的所有元素之和不超过 $x$,并且其面积(包含元素的数量)应尽可能大。
输出满足条件的子矩阵的最大可能面积(即包含元素的最大可能数量)。
输入格式
第一行包含两个整数 $n,m$。
第二行包含 $n$ 个整数 $a_{1},a_{2}, \dots ,a_{n}$。
第三行包含 $m$ 个整数 $b_{1},b_{2}, \dots ,b_{m}$。
第四行包含一个整数 $x$。
输出格式
一个整数,表示满足条件的子矩阵的最大可能面积(即包含元素的最大可能数量)。
如果不存在满足条件的子矩阵,则输出 $0$。
数据范围
前三个测试点满足 $1 \leq n,m \leq 5$。
所有测试点满足 $1 \leq n,m \leq 2000$,$1 \leq a_{i},b_{i} \leq 2000$,$1 \leq x \leq 2 \times {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
解题思路
一开始推导出了求子矩阵和的公式,就是$s = \left( a_{k_{1}} + a_{k_{2}} + \dots a_{k_{n}} \right) \times \left( b_{r_{1}} + b_{r_{2}} + \dots b_{r_{m}} \right)$。先预处理出数组$a$各个长度的区间所对应的区间和,然后让所有求得的和都记录一个对应的最大区间长度,同时对得到的和进行离散化。然后通过枚举数组$b$各个长度的区间所对应的区间和${s'}$,在离散化数组中二分找到小于等于$\left\lfloor \frac{x}{{s'}} \right\rfloor$的值,通过映射得到这个和在数组$a$中对应的最大区间长度,然后与数组$b$的区间长度进行乘积,将这个结果与答案取一个最大值。但这种做法一开始没过,也不知道错哪里,后面改了下又$TLE$了。
首先如果有一个子矩阵,对应的行从$k_{1}$到$k_{n}$,列从$r_{1}$到$r_{m}$,那么子矩阵中第一行的和为$a_{k_{1}} \times \left( {b_{r_{1}} + \dots + b_{r_{m}}} \right)$,同理,一直到第$n$行的和为$a_{k_{n}} \times \left( {b_{r_{1}} + \dots + b_{r_{m}}} \right)$,将每一行的和加起来就得到子矩阵的和,提取公因式$\left( {b_{r_{1}} + \dots + b_{r_{m}}} \right)$,最后会得到$s = \left( a_{k_{1}} + \dots a_{k_{n}} \right) \times \left( b_{r_{1}} + \dots b_{r_{m}} \right)$。
问题就是任选一个$a$的一个区间,任选一个$b$的区间,使得两个区间的和的乘积要小于等于$x$,并且两个区间长度的乘积最大。如果直接枚举的话时间复杂度是$O \left( n^{4} \right)$。
我们考虑一下,对于$a$中长度都为$len$的区间,我们是要在$b$中找到一个尽可能长的区间,使得两个区间的和的乘积小于等于$x$。因为每一个数都是正数,所以区间长度越长,区间和越大。如果有$a$的区间和$s_{a}$,$b$的区间和$s_{b}$,那么在满足$s_{a} \times s_{b} \leq x$的情况下,要让$b$的区间长度越大,意味着$s_{b}$越大,因此要让$s_{a}$越小。因此对于$a$中长度都为$len$的区间,应该选择区间和最小的那个。即长度一定的时候选择区间和最小的那个。这样就从$n^{2}$的自由度降到$n$。数组$b$同理,自由度可以降到$m$。
$s_{a} \left[ i \right]$表示数组$a$中长度为$i$的最小区间和。可以发现$s_{a} \left[ {i+1} \right] > s_{a} \left[ i \right]$。假设有$s_{a} \left[ {i+1} \right] \leq s_{a} \left[ i \right]$,因为每一个元素都是正数,因此我们可以从长度为$i+1$的区间中删除一个数,长度变为$i$,此时的和变成${s'}$,也必然满足${s'} < s_{a} \left[ {i+1} \right] \leq s_{a} \left[ i \right]$,即长度为$i$的区间存在一个比$s_{a} \left[ i \right]$更小的区间和,这就与$s_{a} \left[ i \right]$表示数组$a$中长度为$i$的最小区间和矛盾了。同理$s_{b} \left[ i \right]$。
所以我们可以枚举$i$,当确定了$s_{a} \left[ i \right]$后,找到一个最大的$j$,使得满足$s_{a} \left[ i \right] \times s_{b} \left[ j \right] \leq 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/
标签:dots,right,最大,int,矩阵,leq,区间,left 来源: https://www.cnblogs.com/onlyblues/p/16095332.html