n 個單詞,長度 L[i],每行最多 m 字符 (含單詞間空格)。行代價 = (m - 實際字符數)²。求排版總代價最小值。n ≤ 10000, m ≤ 100。
dp[i][s] = 前 i 個單詞排版完成,第 i 個單詞結尾位於第 s 列 (1≤s≤m) 的最小代價。
轉移:
- 第 i 個單詞單獨佔一行:dp[i][L[i]] = min_s dp[i-1][s] + (m-L[i])²。
- 第 i 個單詞接在第 i-1 個單詞同行之後:需至少一空格,s = prev_s + 1 + L[i] ≤ m。
dp[i][s] = dp[i-1][prev_s] - (m-prev_s)² + (m-s)²。
維護前綴最小值 minPrev[s] = min_{k≤s} dp[i-1][k],將轉移優化至 O(m)。總複雜度 O(n·m)。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10005, MAXM = 105;
const int INF = 0x3f3f3f3f;
int dp[MAXN][MAXM], L[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T; cin >> T;
while (T--) {
int m, n; cin >> m >> n;
for (int i=1; i<=n; ++i) cin >> L[i];
for (int i=0; i<=n; ++i) fill(dp[i], dp[i]+m+1, INF);
dp[0][m] = 0; // 虛擬第 0 行結束於 m
for (int i=1; i<=n; ++i) {
int best = INF;
for (int s=m; s>=0; --s) best = min(best, dp[i-1][s]);
dp[i][L[i]] = best + (m - L[i])*(m - L[i]);
for (int s=L[i]+2; s<=m; ++s) {
int prev = s - L[i] - 1;
if (dp[i-1][prev] != INF)
dp[i][s] = dp[i-1][prev] - (m-prev)*(m-prev) + (m-s)*(m-s);
}
}
int ans = INF;
for (int s=0; s<=m; ++s) ans = min(ans, dp[n][s]);
cout << ans << '\n';
}
return 0;
}