[HNOI2008] 玩具装箱 详细讲解
题目跳转:P3195 [HNOI2008] 玩具装箱
题目大意
共有 \(n\) 个玩具,每个玩具长度 \(C_i\),将 \(j\) 至 \(i\) 连续的玩具放入一个容器 \(1\le j\le i\le n\),其长度 \(x=i-j+\sum_{k=j}^{i}C_k\)。制作一个容器的费用为 \((x-L)^2\),其中 \(L\) 为常数。求将所有玩具都放入容器中的费用最小值。
思路讲解与代码呈现
\(O(n^2)\) 的解法
还是一步一步来,先想暴力。
\(dp\) 设计与状态转移
设 \(dp_{i}\) 表示前 \(i\) 个玩具分好组的最小总费用。
设 \(S_i=\sum_{j=1}^{i}C_j\) 表示前 \(i\) 个玩具长度之和。
如果最后一段是第 \(j+1\) 到第 \(i\) 个玩具,那么这一段的长度为:
整理一下就是:
设 \(A_i=S_i+i\),则最后一段的长度就是:
那么这一段的费用就是:
再设 \(B_j=A_j+L+1\),整理一下就是:
所以状态转移方程就是:
要注意,因为 \(A_0=0\),\(B_0=A_0+L+1\),所以\(B_0=L+1\)。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
ll rd(){
ll x=0,f=1,ch=gc();
while(ch<'0'||ch>'9'){
if(ch=='-'){
f=-1;
}
ch=gc();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch-'0');
ch=gc();
}
return x*f;
}
void write(ll x){
if(x<0){
x=-x;
putchar('-');
}
if(x>9){
write(x/10);
}
putchar(x%10+'0');
}
const ll maxn=5e4+5;
ll n,L,a[maxn],b[maxn],sum[maxn],dp[maxn];
int main(){
n=rd();
L=rd();
b[0]=L+1;
for(ll i=1;i<=n;i++){
sum[i]=rd()+sum[i-1];
a[i]=sum[i]+i;
b[i]=a[i]+L+1;
dp[i]=1e18;
}
for(ll i=1;i<=n;i++){
for(ll j=0;j<i;j++){
ll x=a[i]-b[j];
dp[i]=min(dp[i],dp[j]+x*x);
}
}
write(dp[n]);
return 0;
}
正解:斜率优化
整理状态转移方程
把刚才的状态转移式子展开;
因为 \(A_i^2\) 与 \(j\) 的变化无关,所以提出来:
整理一下:
设 \(y=dp_i-A_i^2\),\(x=A_i\),对于每个决策 \(j\),把它看成一个一次函数:
于是问题转化为求若干直线在 \(x=A_i\) 处的最小值。
如果你做过 P10979 任务安排 2,那你肯定不陌生。没做过也没关系,可以先去我之前写的博客里补习一下:斜率优化DP例题:任务安排 三道题 详细讲解。
注意到:
-
\(C_i\geq 1\);
-
\(S_i=\sum_{j=1}^{i}C_j\),随 \(i\) 增加而增加;
-
\(A_i=S_i+i\),随 \(i\) 增加而增加;
-
\(B_i=A_i+L+1\),也随 \(i\) 增加而增加;
-
直线的斜率是 \(-2B_j\),随 \(j\) 增加而递减。
因此可以用单调队列维护一个下凸壳。
插入新决策
设队列尾部有三个决策 \(q_{r-1},q_r,i-1\)。如果 \(q_{r}\) 已经不可能成为最优决策,就把它弹出。
判断条件为:
其中:
为了避免浮点误差,交叉相乘:
满足时弹出 \(q_r\)。
查询最优决策
如果队列中第二个决策在当前 \(A_i\) 处已经不比第一个差,即:
那么队头决策以后也不会更优,直接弹出。
其中:
弹完队首后记录答案:
时间复杂度
因为每个元素只入队一次,也只出队一次,所以时间复杂度为 \(O(n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
ll rd(){
ll x=0,f=1,ch=gc();
while(ch<'0'||ch>'9'){
if(ch=='-'){
f=-1;
}
ch=gc();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch-'0');
ch=gc();
}
return x*f;
}
void write(ll x){
if(x<0){
x=-x;
putchar('-');
}
if(x>9){
write(x/10);
}
putchar(x%10+'0');
}
const ll maxn=5e4+5;
ll n,L,l=1,r,q[maxn],c,a[maxn],b[maxn],dp[maxn];
ll calc(ll y,ll x){
ll squ=(a[x]-b[y]);
return dp[y]+squ*squ;
}
bool check1(ll x,ll y,ll z){
return calc(x,z)<=calc(y,z);
}
ll val(ll x){
ll squ=b[x];
return dp[x]+squ*squ;
}
bool check2(ll x,ll y,ll z){
return (val(y)-val(x))*(b[z]-b[y])>=(val(z)-val(y))*(b[y]-b[x]);
}
int main(){
n=rd();
L=rd();
b[0]=L+1;
for(ll i=1;i<=n;i++){
c=rd()+c;
a[i]=c+i;
b[i]=a[i]+L+1;
}
for(ll i=1;i<=n;i++){
while(l<r&&check2(q[r-1],q[r],i-1)){
r--;
}
q[++r]=i-1;
while(l<r&&check1(q[l+1],q[l],i)){
l++;
}
dp[i]=calc(q[l],i);
}
write(dp[n]);
return 0;
}

浙公网安备 33010602011771号