[HNOI2008]玩具装箱TOY

Portal

这一题是斜率优化的入门题,应该写一些文字来总结.

斜率优化是一种针对dp方程式中有一个或数个单项式同时含有IJ相关的内容的一种优化。


首先列出Dp方程式:$$ Dp[i] = min{Dp[j] + (sum[i] - sum[j] + i - j - l - 1)^2}$$

I,J,为主元进行分类。我们先忽略Min

严格意义来说,这里的常数项为了保持式子的优美应该单独考虑,这里因为要尽量简化Dp[i]的求解, 我们将其

\[Dp[i] = Dp[j] + ((sum[i] + i) - (sum[j] + j + l + 1)) ^ 2\\ Dp[i] = Dp[j] + (sum[i] + i)^2 + (sum[j] + j + l + 1) ^ 2 - 2 * (sum[i] + i) * (sum[j] + j + l + 1)\\ 令sum[i] + i = A_i, sum[i] + i + l + 1 = B_i\\ Dp[i] = Dp[j] + A_i^2 + B_j^2 - 2 * A_i * B_j\\ 再次分类:\\ dp[j] + B_j^2 = 2 * A_i * B_i + dp[i] - A_i^2 \]

这个式子里,我们初中的点斜式的斜率k就是\(2 * A_i\),截距就是\(dp[i] - A_i^2\) 这里我们知道了斜率,要最小化截距, 并且这里面的点是\((x = B_i, y = dp[j] + B_j^2)\)

发现如果让截距变小的话就维护这些点的一个下凸壳。一个斜率固定的直线一定会跟这个凸壳的斜率最相近的点先碰到。

观察到斜率是单调递增的, 于是我们可以用一个单调队列维护。

最后一定要维护凸壳的凸性质。所以要判断队列的最后三个点组成的三角形是否方向向左。

总结

如果是取最大值,那就维护上凸壳,取最小值,那就维护下凸壳。
如果k(\(2 * A_i\))没有单调性-> 凸壳上二分。

如果x(\(B_i\))-> 数据结构维护。

Codes

#include<bits/stdc++.h>
using std :: deque;
#define rep(i, a, b) for(int i = (a), i##_end_ = (b); i <= i##_end_; ++i)
#define drep(i, a, b) for(int i = (a), i##_end_ = (b); i >= i##_end_; --i)
#define clar(a, b) memset((a), (b), sizeof(a))
#define debug(...) fprintf(stderr, __VA_ARGS__)
typedef long long LL;
typedef long double LD;
int read() {
    char ch = getchar();
    int x = 0, flag = 1;
    for (;!isdigit(ch); ch = getchar()) if (ch == '-') flag *= -1;
    for (;isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
    return x * flag;
}
void write(int x) {
    if (x < 0) putchar('-'), x = -x;
    if (x >= 10) write(x / 10);
    putchar(x % 10 + 48);
}

#define sqr(x) ((x) * (x))
const int Maxn = 50009;
static int n, l, a[Maxn];
static LL sum[Maxn];

void init() {
	n = read(); l = read();
	rep (i, 1, n) {
		a[i] = read();
		sum[i] = sum[i - 1] + a[i];
	}
}

LL dp[Maxn], que[Maxn], fr, ls;
double slope(int a, int b) {
	return (dp[a] + sqr(sum[a] + a + l + 1ll) - dp[b] - sqr(sum[b] + b + l + 1ll)) / (sum[a] + a - sum[b] - b);
}

void solve() {
	rep (i, 1, n) {
		while (fr < ls && slope(que[fr], que[fr + 1]) < 2 * (sum[i] + i)) ++fr;
		dp[i] = dp[que[fr]] + sqr(sum[i] + i - sum[que[fr]] - que[fr] - l - 1);
		while (fr < ls && slope(que[ls], que[ls - 1]) > slope(i, que[ls])) --ls;
		que[++ls] = i;
	}

	printf("%lld\n", dp[n]);
}

int main() {
	freopen("BZOJ1010.in", "r", stdin);
	freopen("BZOJ1010.out", "w", stdout);

	init();
	solve();

#ifdef Qrsikno
    debug("\nRunning time: %.3lf(s)\n", clock() * 1.0 / CLOCKS_PER_SEC);
#endif
    return 0;
}
posted @ 2018-12-18 21:52  Qrsikno  阅读(100)  评论(0编辑  收藏  举报