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

↓这个就是各种化?单调队列写得挺好的,然而斜率优化其实并不怎么看懂ORZORZ

http://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;
}


posted @ 2016-09-22 13:58  OxQ  阅读(143)  评论(0编辑  收藏  举报