题目
有连续的(n)份成绩单,每份有一个分数(w_i)。现在要按照批次发放成绩单,每次选择连续的一段成绩单发放,发放完一轮后将左右两端剩余的合并继续重复操作。定义(k)为发放次数,(max_i)和(min_i)表示每次发放的分数最大值和最小值,以及两个系数(a)和(b),要求最小化:
数据范围:(nleq 50),(1leq w_ileq 1000)。
题解
是一道不错的 DP 题。显然跟区间有关,复杂度估计在(mathcal O(n^5))左右吧。
我们每次发放操作相当于在区间上挖了一个“窟窿”,经过若干次操作挖了一连串的“窟窿”,然后再一次操作经过许多“窟窿”,把剩下的选走。
所以我们设计一个状态(f_{l,r})表示(l)到(r)内,挖了若干“窟窿”的最优解,用(g_{l,r})表示全部取走(l)到(r)的最优解。但这个窟窿怎么挖呢?挖了的窟窿会给状态(f)带来什么影响呢?其中最大的影响莫过于剩余的数的(max_i)和(min_i)了。所以我们还需要增加两维(x)和(y)表示(l)到(r)挖了若干“窟窿”后剩余的最大值为(x),最小值为(y)(由于(n)很小,我们将(w_i)离散化)。而(f_{l,r,x,y}),肯定要往两边(这里选右边)拓展,对于(f_{l,r-1,x,y}),考虑将(r)纳入,窝们有两种选择:
(1)不挖它,代价为(0),转移为
(2)挖它,则(r)一定会在其中一个窟窿内,这个窟窿一定为([k,r],kin[l,r]),代价为(g_{k,r}),即
而由以上又可以得出:(g_{l,r}=minlimits_{xleqslant y}{f_{l,r,x,y}+a+b imes(val_y-val_x)^2})。
初始情况为(f_{i,i,w_i,w_i}=0)以及(g_{i,i}=a),其它的为(infty)。枚举(f)的每一维以及(k)的选取,故时间复杂度为(mathcal O(n^4) imesmathcal O(n)=mathcal O(n^5)),可以通过。
代码
#include <bits/stdc++.h>
#define min(a, b) (a) < (b) ? (a) : (b)
#define max(a, b) (a) > (b) ? (a) : (b)
#define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define chkmax(a, b) a = max(a, b)
#define chkmin(a, b) a = min(a, b)
const int maxn = 55;
int n, a, b, w[maxn], disc[maxn];
int f[maxn][maxn][maxn][maxn], g[maxn][maxn];
inline int sqr(int x) { return x * x; }
int main() {
n = read(), a = read(), b = read();
rep(i, 1, n) disc[i] = w[i] = read();
// 离散化
std::sort(disc+1, disc+n+1);
rep(i, 1, n) w[i] = std::lower_bound(disc+1, disc+n+1, w[i]) - disc;
// 初始化
memset(f, 0x3f, sizeof f), memset(g, 0x3f, sizeof g);
rep(i, 1, n) f[i][i][w[i]][w[i]] = 0, g[i][i] = a;
// 做DP
rep(len, 1, n) // 枚举区间长度
rep(l, 1, n-len+1) {
int r = l+len-1; // 区间[l,r]
rep(x, 1, n)
rep(y, x, n) {
chkmin(f[l][r][min(x, w[r])][max(y, w[r])], f[l][r-1][x][y]); // 情况1
rep(k, l, r) chkmin(f[l][r][x][y], f[l][k-1][x][y] + g[k][r]); // 情况2
}
rep(x, 1, n)
rep(y, x, n)
chkmin(g[l][r], f[l][r][x][y] + a + b * sqr(disc[y] - disc[x])); // 维护g[l][r]
}
printf("%d", g[1][n]); // 答案为g[1][n]
return 0;
}