题意:有一排仓库,每个仓库都有个价值,可以炸掉两个仓库之间的铁路,如果仓库按顺序是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;
}