[HNOI2008]玩具装箱
「HNOI2008」玩具装箱
题目大意
有 个玩具,第 个玩具的价值为 。这 个玩具排成一排,要求将这些玩具分成若干段,对于一段 ,其代价为 。其中 是一个常量,求分段最小代价。
题解(斜率优化)
比较容易想到的是直接dp做:
设 为前i个物品分成若干段的最小代价。
那么状态转移方程为:
那么这样的做法时间复杂度是 的,是过不了的。
考虑优化:
对于上述状态转移方程,我们在进行变换:
为方便理解,令 等于 , 。原式子等于 。
将与 有关的放一边,我们能得到:
我们要找到一个 使得 最小,我们便设与 相关的数设为变量。通过上面的式子我们可以发现,若我们将一次函数的斜截式 代入其中,也就是 ,将 相关的令作常量,与 相关的令作变量,即:
上述转移方程可以写成 ,首先 是一个常量,我们把 看成平面直角坐标系上的点,这样我们就把原问题转化成了选择一个合适的点 ,使得截距 最小。
如上图所示,我们将图中的直线向上平移,第一个接触到的点B显然是使得截距最小的点,且我们会发现能使得截距最小的点一定是下凸点,以点B为例,前一个点A,和后一个点B构成的斜率满足 ,所以点B是下凸点,同时点B是合适点的另一条件是 ,且本题中, 是递增的,所以,我们可以用一个单调队列维护下凸点(即连续点的斜率递增的点),步骤如下:
- 首先是在队首 ,判断是否 ,如果不是说明点 并不是合适点,删除即可,直到满足 。
- 之后根据合适点更新
- 加入新的点进入队尾 前,先判断 ,如果不成立,则队尾的点不是下凸点,删除并且找到满足的即可
对于步骤1,由于 的增大,我们发现点A,B都不满足合适点的要求了,直接贪心的删去就好了。
对于步骤3,新加入点E后,点D并不构成下凸点的条件,无论 变成什么,点D都不可能是合适点,那么直接在队尾中删去
具体实现看下列代码
#include <algorithm>
#include <cstdio>
using ll = long long;
const int N = 5e4 + 5;
ll dp[N], pre[N], q[N], n, L, head, tail;
ll a(int i){
return pre[i] + i;
}
ll Y(int i){
return dp[i] + a(i) * a(i);
}
double K(int i, int j){
return 1. * (Y(i) - Y(j)) / (a(i) - a(j));
}
int main(void){
scanf("%lld%lld", &n, &L);
L++;
for(int i = 1; i <= n; ++i){
int a;
scanf("%d", &a);
pre[i] = pre[i - 1] + a;
}
head = tail = 1;
for(int i = 1; i <= n; ++i){
while(head < tail && K(q[head], q[head + 1]) < 2 * (a(i) - L))head++;
dp[i] = dp[q[head]] + (a(i) - a(q[head]) - L) * (a(i) - a(q[head]) - L);
while(head < tail && K(q[tail], q[tail - 1]) > K(i, q[tail]))tail--;
q[++tail] = i;
}
printf("%lld", dp[n]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现