bzoj1010[HNOI2008] 玩具装箱toy(带斜率优化学习笔记)
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1010
题目大意:
有n个数,分成连续的若干段,每段(假设从第j个到第i个组成一段)的分数为 (X-L)^2,X为j-i+Sigma(Ck) i<=k<=j,其中L是一个常量。目标:各段分数的总和最小。
题解:
斜率优化
===============带个笔记(总结)~================
斜率优化学了两种方法之后觉得截距式很好用啊
写出dp方程之后就有目的的去化式子了
于是所以我们的目标是要化成f[i]=a[i]*b[j]+c[j]+(一个只与i有关的常数)这个形式!
[a[i]表示一个与i有关的数,b[j]、c[j]亦如此]
移项:-a[i]*b[j]+f[i]=c[j] 有没有很像y=kx+b
把-a[i]看为斜率,b[j]为x,c[j]为y,画一条直线
那么答案f[i]就是直线与y轴的交点,即截距
而为什么去维护上/下凸包可以理解成线性规划~
这两篇都是讲斜率优化的
截距式的,看这个看懂了~http://blog.csdn.net/balloons2012/article/details/7912296
↓这个就是各种化?单调队列写得挺好的,然而斜率优化其实并不怎么看懂ORZORZhttp://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html
==================真の题解====================
至少先把dp方程写出来吧:f[i]=min( f[j] +[sum[i]-sum[j]+i-(j+1)-L]^2 ),j<=i (sum[i]为前缀和
f[i]表示i为其中一个断点,搞完前i个数的最小总和
为了简化方程,我们设s[i]=sum[i]+i;L=L+1;
那么原方程则为 f[i]=min(f[j]+(s[i]-s[j]-L)^2),j<=i
平方拆开、移项能得到:2*(s[i]-L)*s[j]+f[i]=f[j]+s[j]^2
k=2*(s[i]-L);x=s[j];y=f[j]+s[j]*2;
因为求最小值 故维护一个下凸包
p.s.可能我的式子把常数那个部分给吃了orz草稿太乱看不清之前推的式子了大概就这样吧~
代码代码:
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long LL; #define maxn 50100 LL s[maxn],list[maxn];LL f[maxn]; double Y(LL j) {return f[j]+s[j]*s[j];} double X(LL j) {return s[j];} double slop(LL j,LL k) { return (Y(k)-Y(j))/(X(k)-X(j)); } int main() { //freopen("a.in","r",stdin); //freopen("a.out","w",stdout); LL n,L,i,x,head,tail; scanf("%lld%lld",&n,&L);L++;s[0]=0; for (i=1;i<=n;i++) {scanf("%lld",&s[i]);s[i]+=s[i-1];} for (i=1;i<=n;i++) s[i]+=i; head=1;tail=1;list[1]=0; for (i=1;i<=n;i++) { while (head<tail && slop(list[head],list[head+1])<=2*(s[i]-L)) head++; int j=list[head]; f[i]=f[j]+(s[i]-s[j]-L)*(s[i]-s[j]-L); while (head<tail && slop(list[tail-1],list[tail])>slop(list[tail],i)) tail--; list[++tail]=i; } printf("%lld\n",f[n]); return 0; }