[HNOI2008] 玩具装箱
前言
终于进入新专题了家人们
列举一下还要补的专题
- 矩阵快速幂
- 组合数学
- \(\rm{sos\ dp}\)
不过先不管了, 除了复习, 大部分时候总要向前看的嘛
考试的时候带着耳塞, 不带感觉好吵啊, 但是一直带着会神经衰弱的, 所以只能尝试集中注意力
思路
首先转化题意
给定 \(n\) 条线段, 第 \(i\) 条线段的长度为 \(C_i\) , 我们要将这些线段分开, 其中一个分段 \([L, R]\) 的花费为 \(cost = (R - L + \sum_{i = L}^{R} C_i - W)^2\) , 求最小的花费之和
容易想到 \(\rm{dp}\) , 令 \(dp_{i}\) 表示考虑到第 \(i\) 条线段的最优花费
容易找到转移
\[\begin{align*}
dp_i
& = \min_{j \in [0, i]} dp_j + (i - j - 1 + pre_i - pre_j - W)^2 \\
& = \min_{j \in [0, i]} dp_j + [(pre_i + i) - (pre_j + j) - (W + 1)]^2 \\
令 k &= (W + 1), v_i = pre_i + i \\
& = \min_{j \in [0, i]} dp_j + (v_i - v_j - k)^2 \\
& = \min_{j \in [0, i]} dp_j + (v_i - v_j)^2 + k^2 - 2k(v_i - v_j) \\
& = k^2 + \min_{j \in [0, i]} dp_j + (v_i^2 + v_j^2 - 2v_iv_j) - 2kv_i + 2kv_j \\
& = -k^2 +
\min_{j \in [0, i]} dp_j + (v_i^2 - 2kv_i + k^2) + (v_j^2 + 2kv_j + k^2) - 2v_iv_j \\
& = -k^2 + \min_{j \in [0, i]} dp_j + (v_i- k)^2 + (v_j + k)^2 - 2v_iv_j \\
& = v_i^2 - 2kv_i + \min_{j \in [0, i]} dp_j + (v_j + k)^2 - 2v_iv_j \\
\end{align*}
\]
考虑最终的柿子
\[dp_i = v_i^2 - 2kv_i + \min_{j \in [0, i]} dp_j + (v_j + k)^2 - 2v_iv_j
\]
考虑
\[\min_{j \in [0, i]} dp_j + (v_j + k)^2 - 2v_iv_j
\]
你发现 \(v_iv_j\) 这样的形式, 考虑斜率优化
先不管 \(v_i^2 - 2kv_i\) , 我们写出柿子
\[dp_i = \min_{j \in [0, i]} dp_j + (v_j + k)^2 - 2v_iv_j
\]
拆掉 \(\min\)
\[dp_i = dp_j + (v_j + k)^2 - 2v_iv_j
\]
\[\Downarrow
\]
\[dp_j + (v_j + k)^2 = 2v_iv_j - dp_i
\]
套路的, 令 \(x = v_j, y = dp_j + (v_j + k)^2, k = 2v_i, b = -dp_i\)
柿子转化为
\[y = kx + b
\]
考虑其性质满足 \(k, x\) 单调递增, 我们简单的运用斜率优化, 使得 \(b\) 最大
\[{dp_i}_{\min} = -b_{\max} + v_i^2 - 2kv_i
\]
考虑实现,
由于 \(k, x\) 的单增, 我们考虑使用单调队列维护上凸包
单调队列维护点即可
实现
框架
直接按照上述内容实现即可
代码
#include <bits/stdc++.h>
#define int long long
const int MAXN = 5e4 + 20;
int n, K;
int C[MAXN];
int dp[MAXN]; // 状态
int pre[MAXN], v[MAXN]; // 前缀和及其导出
namespace slope {
int X(int a) { return v[a]; }
int Y(int a) { return dp[a] + (v[a] + K) * (v[a] + K); }
int slope_up(int a, int b) { return Y(b) - Y(a); }
int slope_down(int a, int b) { return X(b) - X(a); }
} using namespace slope;
/*维护下凸包的单调队列*/
struct monoqueue {
std::deque<int> q;
bool checktail(int x) {
return slope_up(x, q.back()) * slope_down(q.back(), q.at(q.size() - 2)) <= slope_up(q.back(), q.at(q.size() - 2)) * slope_down(x, q.back());
}
/*找到当前斜率下最优的情况*/
int get(int k) {
/*弹出不满足下凸包性质的点*/ while (q.size() >= 2 && slope_up(q.front(), q.at(1)) <= k * slope_down(q.front(), q.at(1))) q.pop_front();
return q.front();
}
/*插入当前点*/
void insert(int x) {
/*弹出不满足下凸包性质的点*/ while (q.size() >= 2 && checktail(x)) q.pop_back();
q.push_back(x);
}
} mq;
signed main()
{
scanf("%lld %lld", &n, &K); K++;
for (int i = 1; i <= n; i++) scanf("%lld", &C[i]), pre[i] = pre[i - 1] + C[i], v[i] = pre[i] + i;
/*初始化*/
dp[0] = 0;
mq.insert(0);
for (int i = 1; i <= n; i++) {
int j = mq.get(2 * v[i]);
dp[i] = dp[j] + (i - j + pre[i] - pre[j] - K) * (i - j + pre[i] - pre[j] - K);
mq.insert(i);
}
printf("%lld", dp[n]);
return 0;
}
总结
常见的 \(\rm{dp}\) 状态设计
优化不来可以考虑继续研究柿子
常见的斜率优化, 拆柿子能力史诗级提升

浙公网安备 33010602011771号