ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

最大子矩阵

2022-04-03 11:00:38  阅读:157  来源: 互联网

标签:dots right 最大 int 矩阵 leq 区间 left


最大子矩阵

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有