# APCS實作題2022年6月第4題:內積
> 日期:2024年8月3日
> 作者:王一哲
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=i402)
> [ZeroJudge 題目連結時限放寬版](https://zerojudge.tw/ShowProblem?problemid=i429)
## 題目
### 問題描述
輸入兩個長度分別為 $n$ 和 $m$ 的陣列 $A_1, A_2, \dots, A_n$ 以及 $B_1, B_2, \dots, B_m$。你可以自由決定是否要將 $A, B$ 做翻轉(reverse),也可以自由決定一個正整數 $r$。目標要在 $A, B$ 分別找一個長度 $r$ 的子區間(subarray),並讓這兩個子區間的內積最大化。
內積的定義如下:
假設在 $A$ 陣列選出了一段長度 $r$ 的子區間 $A_i, A_{i+1}, A_{i+2}, \dots, A_{i+r-1}$,並在 $B$ 陣列選出了一段長度 $r$ 的子區間 $B_j, B_{j+1}, B_{j+2}, \dots, B_{j+r-1}$,這兩個子區間的內積就是 $A_i B_j + A_{i+1} B_{j+1} + A_{i+2} B_{j+2} + \dots + A_{i+r-1} B_{j+r-1}$,也可以寫成 $\sum_{k=0}^{r-1} A_{i+k} B_{j+k}$。
<br />
### 輸入說明
第一行輸入兩個正整數 $n, m ~(1 \leq n, m \leq 1000)$,接下來一行有 $n$ 個整數 $A_1, A_2, \dots, A_n$,接下來一行有 $m$ 個整數 $B_1, B_2, B_m$,陣列的數值絕對值均不超過 100。
子題配分
- 20%:$1 \leq n, m \leq 200$
- 80%:無額外限制
<br />
### 輸出格式
輸出一個整數代表內積最大值。
<br />
### 範例輸入1
```
5 5
-3 -3 3 3 -3
2 2 2 2 2
```
### 範例輸出1
```
12
```
### 範例輸入2
```
5 5
-3 -3 -3 5 -5
-5 5 -3 -3 -3
```
### 範例輸出2
```
77
```
### 範例輸入3
```
4 3
1 2 3 4
-1 -2 -3
```
### 範例輸出3
```
-1
```
<br /><br />
## Python 程式碼
### 寫法1,用二維串列記錄內積的最大值,會超時
如果串列A的長度為n、串列B的長度為m,建立一個 (n+1)\*(m+1) 的二維串列 dp,所有資料皆預設為0,dp[i][j] 為串列 A[0] ~ A[i-1]、 B[0] ~ B[i-1] 內積的最大值,對應到程式碼的第7行
```python
dp[i][j] = max(dp[i-1][j-1] + a[i-1]*b[j-1], a[i-1]*b[j-1])
```
如果 dp[i-1][j-1] < 0,則取 a[i-1]\*b[j-1]。
這個寫法在第4筆測資開始超時。
```python=
def solve(a, b): # 自訂函式,輸入串列a,b,找內積最大值
imax = -100000 # 目前的最大值,由於題目限制串列資料絕對值小於100,相乘後小於10000,不可能小於預設值
na, mb = len(a), len(b) # 串列a、b的長度
dp = [[0]*(mb+1) for _ in range(na+1)] # 儲存內積最大值用的二維串列
for i in range(1, na+1):
for j in range(1, mb+1):
dp[i][j] = max(dp[i-1][j-1] + a[i-1]*b[j-1], a[i-1]*b[j-1])
imax = max(imax, dp[i][j]) # 更新最大值
return imax # 回傳最大值
n, m = map(int, input().split()) # 讀取 n、m
A = list(map(int, input().split())) # 讀取 A
B = list(map(int, input().split())) # 讀取 B
ans = -100000 # 答案,預設為 -100000
ans = max(ans, solve(A, B)) # 呼叫 solve,代入 A、B 求答案
A = A[::-1] # 反轉串列 A
ans = max(ans, solve(A, B)) # 呼叫 solve,代入反轉後的 A、原來的 B 求答案
B = B[::-1] # 反轉串列 B
ans = max(ans, solve(A, B)) # 呼叫 solve,代入反轉後的 A、反轉後的 B 求答案
A = A[::-1] # 反轉串列 A,轉回原來的順序
ans = max(ans, solve(A, B)) # 呼叫 solve,代入原來的 A、反轉後的 B 求答案
print(ans) # 印出答案
```
<br /><br />
### 寫法2,逐步更新內積最大值
時限放寬版,費時最久約為 1.7 s,使用記憶體最多約為 3.5 MB,通過測試。普通版第4筆測資超時。
```python=
def solve(a, b): # 自訂函式,輸入串列a,b,找內積最大值
imax = -100000 # 目前的最大值,預設為 -100000
na, mb = len(a), len(b) # 串列a、b的長度
for i in range(na): # 依序檢查 a[0] ~ a[na-1]
tot, p, q = 0, i, 0 # 目前的內積加總 tot,由 a 讀資料的索引值 p,由 b 讀資料的索引值 q
while p < na and q < mb: # 如果 p、q 都還沒有出界時繼續執行
if tot < 0: tot = 0 # 如果 tot 小於 0,重設為 0
tot += a[p]*b[q] # 更新 tot,加上 a[p]*b[q]
imax = max(imax, tot) # 更新 imax
p += 1; q += 1 # 更新p、q
return imax # 回傳 imax
n, m = map(int, input().split()) # 讀取 n、m
A = list(map(int, input().split())) # 讀取 A
B = list(map(int, input().split())) # 讀取 B
ans = -100000 # 答案,預設為 -100000
ans = max(ans, solve(A, B)) # 呼叫 solve,代入 A、B 求答案
A = A[::-1] # 反轉串列 A
ans = max(ans, solve(A, B)) # 呼叫 solve,代入反轉後的 A、原來的 B 求答案
B = B[::-1] # 反轉串列 B
ans = max(ans, solve(A, B)) # 呼叫 solve,代入反轉後的 A、反轉後的 B 求答案
A = A[::-1] # 反轉串列 A,轉回原來的順序
ans = max(ans, solve(A, B)) # 呼叫 solve,代入原來的 A、反轉後的 B 求答案
print(ans) # 印出答案
```
<br /><br />
以上的程式碼中,第6 ~ 10行也可以改用 for 迴圈。時限放寬版,費時最久約為 1.3 s,使用記憶體最多約為 3.5 MB,通過測試。
```python=
tot = 0 # 目前的內積加總 tot
for p, q in zip(range(i, na), range(mb)): # 如果 p、q 都還沒有出界時繼續執行
if tot < 0: tot = 0 # 如果 tot 小於 0,重設為 0
tot += a[p]*b[q] # 更新 tot,加上 a[p]*b[q]
imax = max(imax, tot) # 更新 imax
```
<br /><br />
## C++ 程式碼
### 寫法1,用二維串列記錄內積的最大值
普通版及時限放寬版,費時最久約為 14 ms,使用記憶體最多約為 15.5 MB,通過測試。
```cpp=
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int solve(vector<int> a, vector<int> b) { // 自訂函式,輸入陣列a, b,找內積最大值
int imax = -100000; // 目前的最大值,由於題目限制串列資料絕對值小於100,相乘後小於10000,不可能小於預設值
int n = (int)a.size(), m = (int)b.size(); // 陣列a、b的長度
vector<vector<int>> dp (n+1, vector<int> (m+1, 0)); // 儲存內積最大值用的二維串列
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
dp[i][j] = max(dp[i-1][j-1] + a[i-1]*b[j-1], a[i-1]*b[j-1]);
imax = max(imax, dp[i][j]); // 更新最大值
}
}
return imax; // 回傳最大值
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); // 加速
int n, m; cin >> n >> m; // 讀取 n、m
vector<int> A (n, 0), B (m, 0); // 陣列A、B
for(int i=0; i<n; i++) cin >> A[i]; // 讀取 A
for(int i=0; i<m; i++) cin >> B[i]; // 讀取 B
int ans = -100000; // 答案,預設為 -100000
ans = max(ans, solve(A, B)); // 呼叫 solve,代入 A、B 求答案
reverse(A.begin(), A.end()); // 反轉 A
ans = max(ans, solve(A, B)); // 呼叫 solve,代入反轉後的 A、原來的 B 求答案
reverse(B.begin(), B.end()); // 反轉 B
ans = max(ans, solve(A, B)); // 呼叫 solve,代入反轉後的 A、反轉後的 B 求答案
reverse(A.begin(), A.end()); // 反轉 A,轉回原來的順序
ans = max(ans, solve(A, B)); // 呼叫 solve,代入原來的 A、反轉後的 B 求答案
cout << ans << "\n"; // 印出答案
return 0;
}
```
<br /><br />
### 寫法2,逐步更新內積最大值
普通版及時限放寬版,費時最久約為 5 ms,使用記憶體最多約為 380 kB,通過測試。
```cpp=
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int solve(vector<int> a, vector<int> b) { // 自訂函式,輸入陣列a, b,找內積最大值
int imax = -100000; // 目前的最大值,由於題目限制串列資料絕對值小於100,相乘後小於10000,不可能小於預設值
int n = (int)a.size(), m = (int)b.size(); // 陣列a、b的長度
for(int i=0; i<n; i++) { // 依序檢查 a[0] ~ a[n-1]
int tot = 0; // 目前的內積加總
for(int p=i, q=0; p<n && q<m; p++, q++) { // 如果 p、q 都還沒有出界時繼續執行
if (tot < 0) tot = 0; // 如果 tot 小於 0,重設為 0
tot += a[p]*b[q]; // 更新 tot,加上 a[p]*b[q]
imax = max(imax, tot); // 更新最大值
}
}
return imax; // 回傳最大值
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); // 加速
int n, m; cin >> n >> m; // 讀取 n、m
vector<int> A (n, 0), B (m, 0); // 陣列A、B
for(int i=0; i<n; i++) cin >> A[i]; // 讀取 A
for(int i=0; i<m; i++) cin >> B[i]; // 讀取 B
int ans = -100000; // 答案,預設為 -100000
ans = max(ans, solve(A, B)); // 呼叫 solve,代入 A、B 求答案
reverse(A.begin(), A.end()); // 反轉 A
ans = max(ans, solve(A, B)); // 呼叫 solve,代入反轉後的 A、原來的 B 求答案
reverse(B.begin(), B.end()); // 反轉 B
ans = max(ans, solve(A, B)); // 呼叫 solve,代入反轉後的 A、反轉後的 B 求答案
reverse(A.begin(), A.end()); // 反轉 A,轉回原來的順序
ans = max(ans, solve(A, B)); // 呼叫 solve,代入原來的 A、反轉後的 B 求答案
cout << ans << "\n"; // 印出答案
return 0;
}
```
<br /><br />
---
###### tags:`APCS`、`Python`、`C++`