简洁的学习
更进一步的学习

小总结

  1. 优化目的:在依次计算 d p [ j ] dp[j] dp[j]能快速找到前方最优的 j j j使得从 j j j i i i的转移最优;若不优化,整体复杂度应为 O ( n 2 ) O(n^2) O(n2),优化后为 O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n) O(n)(在于斜率 k k k i i i是否单调)
  2. 关键:能推出直线方程 k x + b = y kx+b=y kx+b=y,其中:
    b b b:包含当前状态 d p [ i ] dp[i] dp[i]以及一些常数(只与 i i i相关)
    y y y:由只与 j j j相关的项组成
    k x kx kx:由包含 i i i j j j的交叉项组成(其中 k k k只包含与 i i i有关的项, x x x只包含与 j j j有关的项)
  3. 多样性:所推出的 k x + b = y kx+b=y kx+b=y方程具有多样性,同一个转移方程可以转化为多种正确的直线方程,例如:
    原转移方程: d p [ i ] = d p [ j ] + ( s [ i ] s [ j ] L ) 2 dp[i]=dp[j]+(s[i]-s[j]-L)^2 dp[i]=dp[j]+(s[i]s[j]L)2
    直线方程 1 1 1 2 s [ i ] ( s [ j ] + L ) + d p [ i ] s [ i ] 2 = d p [ j ] + ( s [ j ] + L ) 2 2*s[i]*(s[j]+L)+dp[i]-s[i]^2=dp[j]+(s[j]+L)^2 2s[i](s[j]+L)+dp[i]s[i]2=dp[j]+(s[j]+L)2
    直线方程 2 2 2 2 s [ i ] s [ j ] + d p [ i ] s [ i ] 2 + 2 s [ i ] L L 2 = d p [ j ] + s [ j ] 2 + 2 s [ j ] L 2*s[i]*s[j]+dp[i]-s[i]^2+2*s[i]*L-L^2=dp[j]+s[j]^2+2*s[j]*L 2s[i]s[j]+dp[i]s[i]2+2s[i]LL2=dp[j]+s[j]2+2s[j]L
  4. 算法三大核心:
    1. 斜率函数slope:用直线方程中的 x x x y y y表达式计算 x 1 , y 1 , x 2 , y 2 x1,y1,x2,y2 x1,y1,x2,y2,求出斜率(double)
    2. 单调队列中寻找最优点:注意若 k [ i ] k[i] k[i]能保证单调,则可进行head++的操作;否则用二分
    3. 将当前状态插入单调队列:维护一个凸包

玩具装箱

题意: 给定每个物品长度,同时打包一些连续的物品会有代价,求打包所有的物品的最小花费

思路:写出转移方程,去掉 m i n min min,然后将转移方程化成直线方程(化简过程使用了一些简化方程的手法,细节参见其他博客),注意本题要用long long或者double

如下代码所用的直线方程为: 2 s [ i ] ( s [ j ] + L ) + d p [ i ] s [ i ] 2 = d p [ j ] + ( s [ j ] + L ) 2 2*s[i]*(s[j]+L)+dp[i]-s[i]^2=dp[j]+(s[j]+L)^2 2s[i](s[j]+L)+dp[i]s[i]2=dp[j]+(s[j]+L)2

//#pragma comment(linker, "/STACK:102400000,102400000")
#include "bits/stdc++.h"
#define pb push_back
#define ls l,m,now<<1
#define rs m+1,r,now<<1|1
#define hhh printf("hhh\n")
#define see(x) (cerr<<(#x)<<'='<<(x)<<endl)
using namespace std;
typedef long long ll;
typedef pair<ll,int> pr;
inline ll read() {ll x=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();return x;}
 
const int maxn = 1e5+10;
const int mod = 1e9+7;
const double eps = 1e-9;

ll N, L;
ll s[maxn], q[maxn];
ll dp[maxn];

inline double slope(int i, int j) {
	ll x1=s[i]+L, x2=s[j]+L;
	ll y1=dp[i]+(s[i]+L)*(s[i]+L);
	ll y2=dp[j]+(s[j]+L)*(s[j]+L);
	return double(y2-y1)/(x2-x1);
}

int main() {
    //ios::sync_with_stdio(false);
    N=read(), L=read()+1; //L+1为了简化方程
	for(int i=1; i<=N; ++i) s[i]=read(), s[i]+=s[i-1]; //前缀和
	for(int i=1; i<=N; ++i) s[i]+=i; //s[i]+=i也为了简化方程
	int head=0, tail=0;
	for(int i=1; i<=N; ++i) {
		while(head<tail&&slope(q[head],q[head+1])-eps<=2*s[i]) head++; //找最优转移点
		dp[i]=dp[q[head]]+(s[i]-s[q[head]]-L)*(s[i]-s[q[head]]-L);
		while(head<tail&&slope(q[tail-1],i)<slope(q[tail-1],q[tail])) tail--; //将此点插入队列,并维护一个下凸包
		q[++tail]=i;
	}
	printf("%lld\n", dp[N]);
}