其他分享
首页 > 其他分享> > 最大子矩阵

最大子矩阵

作者:互联网

最大子矩阵

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