[HNOI2008]玩具装箱
题目描述
P 教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。
P 教授有编号为 1 ⋯ n 1 \cdots n 1⋯n 的 n n n 件玩具,第 i i i 件玩具经过压缩后的一维长度为 C i C_i Ci。
为了方便整理,P教授要求:
-
在一个一维容器中的玩具编号是连续的。
-
同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物。形式地说,如果将第 i i i 件玩具到第 j j j 个玩具放到一个容器中,那么容器的长度将为 x = j − i + ∑ k = i j C k x=j-i+\sum\limits_{k=i}^{j}C_k x=j−i+k=i∑jCk
制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为 x x x,其制作费用为 ( x − L ) 2 (x-L)^2 (x−L)2。其中 L L L 是一个常量。P 教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过 L L L。但他希望所有容器的总费用最小。
输入格式
第一行有两个整数,用一个空格隔开,分别代表 n n n 和 L L L。
第 2 2 2 到 第 ( n + 1 ) (n + 1) (n+1) 行,每行一个整数,第 ( i + 1 ) (i + 1) (i+1) 行的整数代表第 i i i 件玩具的长度 C i C_i Ci。
输出格式
输出一行一个整数,代表所有容器的总费用最小是多少。
样例 #1
样例输入 #1
5 4
3
4
2
1
4
样例输出 #1
1
提示
对于全部的测试点, 1 ≤ n ≤ 5 × 1 0 4 1 \leq n \leq 5 \times 10^4 1≤n≤5×104, 1 ≤ L ≤ 1 0 7 1 \leq L \leq 10^7 1≤L≤107, 1 ≤ C i ≤ 1 0 7 1 \leq C_i \leq 10^7 1≤Ci≤107
题解
这道题的许多题解都是用斜率优化DP做的,现在我给大家讲一下 1 D / 1 D 1D/1D 1D/1D动态规划中的决策单调性优化的方法来做这道题。
首先,我们可以很容易地求出这题的 1 D / 1 D 1D/1D 1D/1D的动态规划方程:
f ( i ) = min j = 1 i − 1 f j + w j + 1 , i f(i)=\min\limits_{j=1}^{i-1}f_j+w_{j+1,i} f(i)=j=1mini−1fj+wj+1,i,其中 w i , j = ( j − i + ∑ k = i j c k − L ) 2 w_{i,j}=(j-i+\sum\limits_{k=i}^jc_k-L)^2 wi,j=(j−i+k=i∑jck−L)2
设 k ( i ) k(i) k(i)表示状态 i i i取到最优值时的决策,则
∀ i ≤ j , k ( i ) ≤ k ( j ) \forall i\leq j,k(i)\leq k(j) ∀i≤j,k(i)≤k(j)当且仅当:
∀ i ≤ j , w i , j + w i + 1 , j + 1 ≤ w i + 1 , j + w i , j + 1 \forall i\leq j,w_{i,j}+w_{i+1,j+1}\leq w_{i+1,j}+w_{i,j+1} ∀i≤j,wi,j+wi+1,j+1≤wi+1,j+wi,j+1
以上是有关四边形不等式的知识,大家可以自行学习,这里不再赘述。
根据计算,DP式中的 w w w数组是满足上述条件的,所以 k ( i − 1 ) ≤ k ( i ) k(i-1)\leq k(i) k(i−1)≤k(i)。于是,在枚举 i i i的时候,可以从 k ( i − 1 ) k(i-1) k(i−1)开始枚举。但是,虽然减少了枚举次数,但在最坏情况下,时间复杂度还是 O ( n 2 ) O(n^2) O(n2)的。
我们可以换一个角度,每得出一个 f ( i ) f(i) f(i),考虑 f ( i ) f(i) f(i)能更新的状态有哪些。因为满足单调性,我们只需要找到第一个 f ( i ) f(i) f(i)能更新的状态,那么从这个状态到最后一个状态 f ( i ) f(i) f(i)都可以更新。
于是,我们可以用一个栈来维护数据。栈中的每个元素保存一个决策能更新到的起始位置和终止位置。当插入一个新位置时从栈中最后一个元素扫描,如果新决策能更新栈尾的决策的起始点,则将栈尾的元素删除出栈;如果不能更新,则在栈尾元素的起始位置和终止位置中二分查找第一个点 t t t,使该点能够被新决策更新。栈尾决策的终止点改为 t t t前一个点,新元素的起始点为 t t t,终止点为最后一个点。
总时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
code
#include<bits/stdc++.h>
using namespace std;
int n,tp;
long long l,c[50005],sum[50005],f[50005];
struct node{int l,r,id;
}v[50005];
int find(int op){int l=1,r=tp,mid;while(l<=r){mid=(l+r)/2;if(op>v[mid].r) l=mid+1;else if(op<v[mid].l) r=mid-1;else if(op>=v[mid].l&&op<=v[mid].r) return v[mid].id;}return 0;
}
long long tw(long long a){return a*a;
}
long long w(int i,int j){return tw(j-i+sum[j]-sum[i-1]-l);
}
bool pd(int i,int j,int k){return f[i]+w(i+1,k)<f[j]+w(j+1,k);
}
void pt(int t){while(tp){if(v[tp].l>t&&pd(t,v[tp].id,v[tp].l)) --tp;else break;}int l=v[tp].l,r=v[tp].r,mid,ch=v[tp].r+1;while(l<=r){mid=(l+r)/2;if(pd(t,v[tp].id,mid)){ch=mid;r=mid-1;}else l=mid+1;}v[tp].r=ch-1;if(ch<=n) v[++tp]=(node){ch,n,t};
}
int main()
{scanf("%d%lld",&n,&l);for(int i=1;i<=n;i++){scanf("%lld",&c[i]);sum[i]=sum[i-1]+c[i];}v[++tp]=(node){1,n,0};for(int i=1;i<=n;i++){int j=find(i);f[i]=f[j]+w(j+1,i);pt(i);}printf("%lld",f[n]);
}