[HNOI2008]玩具装箱TOY(斜率优化)

题目链接
题意:有编号为 1 ⋯ N 1\cdots N 1N的N件玩具,第 i 件玩具经过压缩后变成一维长度为 C i C_i Ci​ 。要求在一个容器中的玩具编号是连续的,同时如果将第 i 件玩具到第 j 个玩具放到一个容器中,那么容器的长度将为 x = j − i + ∑ k = i j C k x=j-i+\sum\limits_{k=i}^{j}C_k x=ji+k=ijCk。​如果容器长度为 x ,其制作费用为 ( X − L ) 2 (X-L)^2 (XL)2 .其中 L 是一个常量。容器数目长度不限。求最小费用。
1 ≤ N ≤ 50000 , 1 ≤ L , C i ≤ 1 0 7 1 \le N \le 50000,1 \le L,Ci \le 10^7 1N50000,1L,Ci107

这道题是斜率优化的经典题了qvq
当然dp顺序肯定是从前到后了
分析一下答案式
用f(j)来更新f(i)
X − L = i − ( j + 1 ) + ∑ k = j + 1 i C k − L = s u m [ i ] + i − s u m [ j ] − j − L − 1 X - L = i-(j + 1)+\sum\limits_{k=j + 1}^{i}C_k - L= sum[i] + i - sum[j] - j - L - 1 XL=i(j+1)+k=j+1iCkL=sum[i]+isum[j]jL1
a [ i ] = s u m [ i ] + i , b [ i ] = s u m [ i ] + i + 1 + L a[i] = sum[i] + i, b[i] = sum[i] + i + 1 + L a[i]=sum[i]+i,b[i]=sum[i]+i+1+L
f [ i ] = f [ j ] + ( X − L ) 2 = f ( j ) + ( a [ i ] − b [ j ] ) 2 f[i] = f[j] + (X - L)^2 = f(j) + (a[i] - b[j]) ^ 2 f[i]=f[j]+(XL)2=f(j)+(a[i]b[j])2
这里面 随j改变的量是 b [ j ] , b [ j ] 2 b[j], b[j]^2 b[j],b[j]2 f [ j ] f[j] f[j]
所以移项得 2 ⋅ a [ i ] ⋅ b [ j ] + f [ i ] − a [ i ] 2 = f [ j ] + b [ j ] 2 2⋅a[i]⋅b[j]+f[i]−a[i]^2=f[j]+b[j]^2 2a[i]b[j]+f[i]a[i]2=f[j]+b[j]2
将b[j]看作x, f [ j ] + b [ j ] 2 f[j]+b[j]^2 f[j]+b[j]2看作y,这个式子就可以看作一条斜率为 2 a [ i ] 2a[i] 2a[i]的直线
f[i]即当上述直线过点 P ( b [ j ] , f [ j ] + b [ j ] 2 ) P(b[j],f[j]+b[j]^2) P(b[j],f[j]+b[j]2)时,直线在y轴的截距加 a [ i ] 2 a[i]^2 a[i]2
而题目即为找这个截距的最小值
由于sum[i]随i递增 所以a[i],b[i]都递增
所以点 1 ⋯ i − 1 1 \cdots i-1 1i1是从左到右排列的
用单调栈维护一下凸包
像做线性规划一样做一个切线就行了
也就是二分斜率(Pj​,Pj+1​) < 2a[i]
update:貌似不用二分
因为a[i]递增要查询的斜率也递增
那单调队列维护就行了qvq

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 1e5 + 5;
const int K = 2e5;
int n, L;
double sum[N], f[N];
int que[N], head, tail;
inline double a(int x){return sum[x]+x;}
inline double b(int x){return sum[x]+x+1+L;}
inline double X(int x){return b(x);}
inline double Y(int x){return f[x]+b(x)*b(x);}//注意这里不可以用define qvq 
//a[i] = sum[i] + i, b[i] = sum[i] + i + 1 + L
//P(b[j],f[j]+b[j]^2)
inline double slope(int x, int y){
 return (Y(y) - Y(x)) / (X(y) - X(x));
}
int main() {
 scanf("%d%d", &n, &L);
 for(int i = 1; i <= n; ++i){
  scanf("%lf", &sum[i]);
  sum[i] += sum[i - 1];
 }
 head = tail = 1;
 for(int i = 1; i <= n; ++i){
  while(head < tail && slope(que[head], que[head + 1]) < 2 * a(i)) ++head;
  f[i] = f[que[head]] + (a(i) - b(que[head])) * (a(i) - b(que[head]));
  //printf("b %lld\n", (long long)(a(i) - b(que[head])));
  while(head < tail && slope(que[tail - 1], que[tail]) > slope(que[tail - 1], i)) --tail;
  que[++tail] = i;
 }
 printf("%lld", (long long)f[n]);
 return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值