题意:有一排仓库,每个仓库都有个价值,可以炸掉两个仓库之间的铁路,如果仓库按顺序是4---5---1---2,那么整个路段的总价值为4 * 5 + 4 * 1 + 4 * 2 + 5 * 1 + 5 * 2 == 49,我们可以炸掉5---1之间的路段,那么总价值将变为4 * 5 + 1 * 2 == 22,求炸掉路段的总价值的最小值。
分析:假设w[a][b]为a仓库到b仓库的总价值,那么w[a][b]的DP方程为\(w[1][i] = w[1][k] + w[k + 1][i] + (v[1] + v[2] + ... v[k]) * (v[k + 1] + ... + v[i])\),可以得到\(w[k + 1][i] = w[1][i] - w[1][k] - sum[k] * (sum[i] - sum[k])\),也就是第k个仓库到第i个仓库的总价值。我们定义f[i][j]为前i个仓库分成j批的总价值,那么\(f[i][j] = min(f[k][j - 1] + w[1][i] - w[1][k] - sum[k] * (sum[i] - sum[k]))(0 <= k < i)\),那么我们通过变形,得到\(f[k][j - 1] - w[1][k] + sum[k]^{2} = f[i][j] - w[1][i] + sum[k] * sum[i]\),我们假设\(f[k][j - 1] - w[1][k] + sum[k]^{2}为y\),\(sum[i]为k\),那么可以得到\(y = k * sum[k] + f[i][j] - w[1][i]\),这样我们就可以利用凸包进行优化。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1005;
int d[N];
int sum[N];
int w[N];
int f[N][N];
int q[N];
int get_y(int k, int j)
{
return f[j - 1][k] - w[k] + sum[k] * sum[k];
}
int main()
{
int n, m;
while (scanf("%d%d", &n, &m) != EOF)
{
if (n == 0 && m == 0) break;
for (int i = 1; i <= n; ++i) scanf("%d", &d[i]);
for (int i = 1; i <= n; ++i)
{
sum[i] = sum[i - 1] + d[i];
w[i] = w[i - 1] + sum[i - 1] * d[i];
}
for (int i = 1; i <= n; ++i) f[0][i] = w[i];
//分成m批
for (int j = 1; j <= m; ++j)
{
int hh = 0, tt = 0;
q[0] = 0;
//前i个仓库分成j批的最小值
for (int i = 1; i <= n; ++i)
{
while (hh < tt && (get_y(q[hh + 1], j) - get_y(q[hh], j)) <= sum[i] * (sum[q[hh + 1]] - sum[q[hh]])) ++hh;
int k = q[hh];
f[j][i] = f[j - 1][k] + w[i] - w[k] - sum[k] * (sum[i] - sum[k]);
while (hh < tt && (get_y(q[tt], j) - get_y(q[tt - 1], j)) * (sum[i] - sum[q[tt]]) >= (get_y(i, j) - get_y(q[tt], j)) * (sum[q[tt]] - sum[q[tt - 1]])) --tt;
q[++tt] = i;
}
}
printf("%d\n", f[m][n]);
}
return 0;
}