1. 程式人生 > 其它 >最大子矩陣

最大子矩陣

最大子矩陣

給定一個長度為 $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/