P4072 征途 Solution
斜率优化大水题。
首先看到方差,想到拆式子。(下面 \(a_i\) 表示每一段路程的长度)
\(m^2 \times S^2=m^2 \times [\frac{1}{m}\sum\limits_{i=1}^m (a_i-\overline a)^2]=m^2 \times (\frac{1}{m}\sum\limits_{i=1}^ma_i^2-2a_i\overline a+\overline a^2)=m\sum\limits_{i=1}^ma_i^2-2a_i\overline a+\overline a^2\)
令 \(sum=\sum\limits_{i=1}^m a_i,\space sum'=\sum\limits_{i=1}^ma_i^2\),则原式 \(=m\sum\limits_{i=1}^ma_i^2-2a_i(\dfrac{sum}{m})+(\dfrac{sum}{m})^2=m\times sum'-2sum^2+sum^2=m \times sum'-sum^2\)
考虑像方差这题的处理方式,将和压入状态,将平方和作为状态表示的答案,即设 \(dp_{i,j,k}\) 表示当前枚举到第 \(i\) 段路径的右端点,为第 \(j\) 次停靠,当前的路径和为 \(k\) 的最小平方和,最后输出 \(\min\{m \times dp_{n,m,k}-k^2\}\) 即可,复杂度为 \(O(N^2M\sum a_i)\),空间巨大。(其实这是看错题的写法)
得分 \(50pts\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
const int M = 2e3 + 10;
int n, m, Max, a[N], sum[N], dp[N][N][M];
int main() {
ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i], sum[i] = sum[i - 1] + a[i];
memset(dp, 0x3f, sizeof(dp)); dp[1][0][0] = 0; Max = sum[n];
for (int i = 2; i <= n + 1; ++i) {
for (int j = 1; j <= m; ++j) {
for (int k = 1; k < i; ++k) {
int tmp = sum[i - 1] - sum[k - 1];
for (int p = Max; p >= tmp; --p) {
dp[i][j][p] = min(dp[i][j][p], dp[k][j - 1][p - tmp] + tmp * tmp);
}
}
}
}
int res = dp[0][0][0];
for (int p = 0; p <= Max; ++p) {
if (dp[n + 1][m][p] == dp[0][0][0]) continue;
res = min(res, dp[n + 1][m][p] * m - p * p);
}
cout << res << endl;
return 0;
}
接着考虑优化。重新看一遍题,发现最后的和一定为 \(\sum a_i\),所以把枚举和的一维去掉,最后输出 \(m \times dp_{n,m,\sum a_i} - (\sum a_i)^2\) 即可。
得分 \(90pts\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e3 + 10;
int n, m, Max, a[N], sum[N], dp[N][N];
int main() {
ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i], sum[i + 1] = sum[i] + a[i];
memset(dp, 0x3f, sizeof(dp)); dp[1][0] = 0; Max = sum[n + 1];
for (int i = 2; i <= n + 1; ++i) {
for (int j = 1; j <= m; ++j) {
for (int k = 1; k < i; ++k) {
int tmp = sum[i] - sum[k];
dp[i][j] = min(dp[i][j], dp[k][j - 1] + tmp * tmp);
}
}
}
cout << dp[n + 1][m] * m - Max * Max << endl;
return 0;
}
由于转移方程内含 \(i\) 与 \(j\) 下标的乘积项,考虑斜率优化。
下面令 \(sum_i=\sum\limits_{j=1}^i a_j\)。
首先,\(dp_{i,j}=\min\{dp_{k,j-1}+(sum_i-sum_k)\}\)。
设 \(1 \le k_1 < k_2 < i \le n\),且 \(k_2\) 优于 \(k_1\)。
则 \(dp_{k_2,j-1}-2sum_i \times sum_{k_2}+sum_{k_2}^2 \le dp_{k_1,j-1}-2sum_i \times sum_{k_1}+sum_{k_1}^2\)。
把 \(i\) 有关的项移到左边 \(2sum_i(sum_{k_1}-sum_{k_2})\le dp_{k_1,j}-dp_{k_2,j}+sum_{k_1}^2-sum_{k_2}^2\)。
那么有 \(2sum_i \ge \dfrac{(dp_{k_1,j-1}+sum_{k_1}^2)-(dp_{k_2,j-1}+sum_{k_2}^2)}{sum_{k_1}-sum_{k_2}}\)(除以负数要变号)。
维护 \(m\) 个单调队列就可以了,第 \(j\) 层的某个状态从 \(j-1\) 层转移而来即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3e3 + 10;
int n, m, Max, a[N], l[N], r[N], sum[N], q[N][N], dp[N][N];
inline double slope(int j, int k1, int k2) {
return 1. * ((dp[k2][j] + sum[k2] * sum[k2]) - (dp[k1][j] + sum[k1] * sum[k1])) / (sum[k2] - sum[k1]);
}
signed main() {
ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i], sum[i] = sum[i - 1] + a[i];
Max = sum[n]; for (int i = 0; i <= m; ++i) l[i] = r[i] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
while (l[j - 1] < r[j - 1] && 2 * sum[i] >= slope(j - 1, q[j - 1][l[j - 1]], q[j - 1][l[j - 1] + 1])) ++l[j - 1];
dp[i][j] = dp[q[j - 1][l[j - 1]]][j - 1] + (sum[i] - sum[q[j - 1][l[j - 1]]]) * (sum[i] - sum[q[j - 1][l[j - 1]]]);
while (l[j] < r[j] && slope(j, q[j][r[j]], i) <= slope(j, q[j][r[j] - 1], q[j][r[j]])) --r[j];
q[j][++r[j]] = i;
}
}
cout << dp[n][m] * m - Max * Max << endl;
return 0;
}