最大子矩陣
最大子矩陣
給定一個長度為 $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 42 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/