[HNOI2008]玩具装箱TOY /【模板】斜率优化

[HNOI2008]玩具装箱TOY /【模板】斜率优化

一.题目简述

​ 给定一组数列,要把他们分成几组,代每个区间[x, y]的代价是 ((y-x+sum_{i=x}^ya[i]-L)^2).

二.解题思路

​ 这题转移的方程都告诉了,可是还是做不起。不过看样子是个DP题,那我们来推方程。

​ 先把令人厌恶的(sum)去掉,使用前缀和来代替。

[f[i]=min(f[j]+(i-j+sum[i]-sum[j]-1-L)^2) ]

(其实可以先去掉min不看)先来手算一下复杂度为(O(n^2)),数据范围没得玩。于是我们果断使用单调队...列。不行啊用不了啊喂!!由于有平方,混进去了一个混着(i,j)的项,不能用单调队列。此时隆重请出今天的主角:斜率优化

三.斜率优化

​ 所谓斜率,就是(y=kx+b)中的k罢了,但是这个东西要怎么优化呢?先将整个式子化简,有了方便有如下两条

  • A[i]=sum[i]+i;
  • B[i]=A[i]+L+1;

那么原式可以化简为

[f[i]=f[j]+(i-j+sum[i]-sum[j]-1-L)^2;\ f[i]=f[j]+(A[i]-B[j])^2\ f[i]=f[j]+A[i]^2+B[j]^2-2*A[i]*B[j]\ 2*A[i]*B[j]+f[i]-A[i]^2=f[j]+B[j]^2; ]

来考虑一下我们手里的条件与目标:(A[i],B[j],f[j]),齐全,目标:(f[i])。可以直接求出,而我们现在要做的是优化,即要以最快的速度找出一个或少量的j来推出f[i].

话题回到斜率,先决条件已充分,现在要找出斜率。我们是要用j推i,所以需要将i,j分开,这句话的数学体现为

[2*A[i]*x+f[i]-A[i]^2=y ]

可以发现x,y都是只跟j有关的项,剩下的项全都是跟i有关的(上面那句话的意思就是这个)。有了这个式子就可以进行计算了,请想象:我手头有一个斜率为(2*A[i])的直线,但是与y轴的交点未知,我可以令他过某些点(这些点都是之前算出来的((B[j],f[j]+B[j]^2))),以此来计算出对应的交点以及f[i],在题目的期望下要求f[i]最小。

[f[i]=b+A[i]^2 ]

b为与y轴交点坐标,(A[i]^2)
常数,所以在这道题中斜线要尽量的靠下且靠右

四.数形结合

​ 现在来形象一点。

根据上文所提,相当于找一个所谓“右下角”的点,最后找到的点体现出来的性质就应该是他和左边的点的斜率<2*a[i]<他与右边点的斜率,可以用这个来作为寻找的依据。

​ 又有几点

  • A[i]是单调递增的,所以在i之后的点必然只会选择i的决策点之后的点,所以在i之前的决策点可以删掉
  • 围成的凸要尽量大,即新加入的点形成的斜率如果小于之前的形成的就可以代替了(这句还是看代码吧,解释的很麻烦)

(其实找点的时候可以二分)

五.CODE

#include<iostream>
#include<cstring>
#include<cstdio>
#include<deque>
#include<algorithm>
using namespace std;
#define m_for(i,a,b) for(int i=a;i<=b;++i)
#define ll long long
ll read(){
	ll res=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')res=res*10+(ch-'0'),ch=getchar();
	return res*f;
}
const int MAXN=500005;
ll n,l,c[MAXN],s[MAXN],f[MAXN];
inline ll make_y(int i){
	return f[i]+(s[i]+i+l+1)*(s[i]+i+l+1);
}
inline ll make_x(int i){
	return (s[i]+i+l+1);
}
inline ll make_k(int x,int y){
	return (make_y(x)-make_y(y))/(make_x(x)-make_x(y));
}
int q[MAXN],head=1,tail=1;
int main(){
	n=read();l=read();
	m_for(i,1,n)s[i]=s[i-1]+(c[i]=read());
	m_for(i,1,n){
		ll ai=s[i]+i;
		while(head<tail&&make_k(q[head],q[head+1])<2*ai)head++;
		int j=q[head];
		f[i]=f[j]+(s[i]-s[j]+i-j-1-l)*(s[i]-s[j]+i-j-1-l);
		while(head<tail&&make_k(q[tail],q[tail-1])>make_k(i,q[tail-1]))tail--;
		q[++tail]=i;
	}
	cout<<f[n];
}
原文地址:https://www.cnblogs.com/clockwhite/p/12244672.html