斜率优化 [ZJOI2007] 仓库建设

发布时间 2023-11-29 19:55:28作者: HL_ZZP

[ZJOI2007] 仓库建设

题目描述

L 公司有 \(n\) 个工厂,由高到低分布在一座山上,工厂 \(1\) 在山顶,工厂 \(n\) 在山脚。

由于这座山处于高原内陆地区(干燥少雨),L公司一般把产品直接堆放在露天,以节省费用。突然有一天,L 公司的总裁 L 先生接到气象部门的电话,被告知三天之后将有一场暴雨,于是 L 先生决定紧急在某些工厂建立一些仓库以免产品被淋坏。

由于地形的不同,在不同工厂建立仓库的费用可能是不同的。第 \(i\) 个工厂目前已有成品 \(p_i\) 件,在第 \(i\) 个工厂位置建立仓库的费用是 \(c_i\)

对于没有建立仓库的工厂,其产品应被运往其他的仓库进行储藏,而由于 L 公司产品的对外销售处设置在山脚的工厂 \(n\),故产品只能往山下运(即只能运往编号更大的工厂的仓库),当然运送产品也是需要费用的,一件产品运送一个单位距离的费用是 \(1\)

假设建立的仓库容量都都是足够大的,可以容下所有的产品。你将得到以下数据:

  • 工厂 \(i\) 距离工厂 \(1\) 的距离 \(x_i\)(其中 \(x_1=0\))。
  • 工厂 \(i\) 目前已有成品数量 \(p_i\)
  • 在工厂 \(i\) 建立仓库的费用 \(c_i\)

请你帮助 L 公司寻找一个仓库建设的方案,使得总的费用(建造费用 + 运输费用)最小。

输入格式

输入的第一行是一个整数 \(n\),代表工厂的个数。

\(2\)\((n + 1)\) 行,每行有三个用空格隔开的整数,第 \((i + 1)\) 行的整数依次代表 \(x_i,~p_i,~c_i\)

输出格式

仅输出一行一个整数,代表最优方案的费用。

样例 #1

样例输入 #1

3
0 5 10
5 3 100
9 6 10

样例输出 #1

32

提示

样例输入输出 \(1\) 解释

在工厂 \(1\) 和工厂 \(3\) 建立仓库,建立费用为 \(10+10=20\) ,运输费用为 \((9-5) \times 3 = 12\),总费用 \(32\)

数据范围与约定

对于 \(20\%\) 的数据,保证 \(n \leq 500\)

对于 \(40\%\) 的数据,保证 \(n \leq 10^4\)

对于 \(100\%\) 的数据,保证 \(1 \leq n \leq 10^6\)\(0 \leq x_i,p_i,c_i < 2^{31}\)

对于任意的 \(1 \leq i < n\),保证 \(x_i < x_{i + 1}\)

设答案为 \(ans\),保证 \(ans + \sum\limits_{i = 1}^{n} p_ix_i < 2^{63}\)
\(f[i]\)表示在\(i\)处建立了一个仓库并且从\(1\)\(i\)所有商品都运到仓库里面的最小代价

\[f[i]=\displaystyle\min_{1\leq k\leq i}(f[k]+sumval(i,k)+c[i]) \]

麻烦就麻烦在这个sumval怎么快速计算
如果是O(n2)的算法,那这个直接预处理就行,也是O(n2)
但是O(n^2)不能接受,这个状态已经是十分简洁了,没有什么优化的空间,我们要优化,首先就要把这个sumval拆开。

\[首先对确定的sumval[i][j],要变成sumval[i][j+1],用x[i]表示i到i+1的距离,cnt[i]表示从1到i的货物总数量\\ 显然 sumval[st][st]=0\\ 所以sumval[st][ed]=\sum_{j=st+1}^{j\leq ed}{p[j]*(x[i]-x[j])}\\ 所以sumval[st][ed]=\sum_{j=st+1}^{j\leq ed}{p[j]*x[i]-p[j]*x[j]}\\ 设prepx[i]表示p[i]*x[i]的前缀和,prep[i]表示p[i]的前缀和\\ sumval[st][ed]=x[i]*prep[i]-x[i]*prep[k]-prepx[i]+prepx[k] \]

所以最终的方程就是

\[f[i]=\displaystyle\min_{1\leq k\leq i}(f[k]+c[i]+x[i]*prep[i]-x[i]*prep[k])-prepx[i]+prepx[k])\\ f[i]=\displaystyle\min_{1\leq k\leq i}(f[k]-x[i]*prep[k]+prepx[k])+c[i]+x[i]*prep[i]-prepx[i]\\ \]

然后式子里面有\(cnt[i] \times prex[k]\) 这明显是斜率优化的形式
那就按照斜率优化化简子吧

\[f[i]=f[k]-x[i]*prep[k]+prepx[k]+c[i]+x[i]*prep[i]-prepx[i]\\ f[k]+prepx[k]=x[i]*prep[k]-c[i]-x[i]*prep[i]+prepx[i]+f[i]\\ \]

很明显的式子,\(x[i]\)自然是单调递增的,k的范围是1到i,单调移动,非常好性质,使我的dp旋转

算是好写的吧。。没有二分,没有平衡树,ZJOI他真的,我哭死

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read() {
	char c=getchar();ll a=0,b=1;
	for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
	for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
ll n;
ll x[2000001],p[2000001],c[2000001];
ll prep[2000001],prepx[2000001],f[2000001];
ll l,r;
ll q[2000001];
inline ll Y(ll k)
{
	return f[k]+prepx[k];
}
inline ll X(ll k)
{
	return prep[k];
}
inline ll chaji(ll k1,ll k2,ll k3)
{
	ll Y1=Y(k3)-Y(k2),Y2=Y(k3)-Y(k1);
	ll X1=prep[k3]-prep[k2],X2=prep[k3]-prep[k1];
	if(X1==X2)return -1;
//	if(X1==X2)return 
	return Y1*X2-Y2*X1;
}
int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();
	for(ll i=1;i<=n;i++)
	{
		x[i]=read();
		p[i]=read();
		prep[i]=prep[i-1]+p[i];
		prepx[i]=prepx[i-1]+p[i]*x[i];
		c[i]=read();
//		if(p[i]==0)i--,n--;
	}
//	f[1]=c[1];
	l=1,r=1;
	for(ll i=1;i<=n;i++)
	{
		while(l<r&&(Y(q[l+1])-Y(q[l]))<=x[i]*(X(q[l+1])-X(q[l])))l++;
		f[i]=f[q[l]]-x[i]*prep[q[l]]+prepx[q[l]]+c[i]+x[i]*prep[i]-prepx[i];
		while(l<r&&(chaji(q[r-1],q[r],i)<0))r--;
		q[++r]=i;
	}
	int cnt=n;
	for(;cnt>=1;cnt--)
	{
		if(p[cnt]!=0)break;
	}
	ll ans=2147483647;
	for(int i=cnt;i<=n;i++)ans=min(ans,f[i]);
	cout<<ans<<endl;
	return 0;
}

哭个p,tnnd,全是坑
首先,这个斜率可能是0,所以要写成叉积的形式
然后,最后可能是连续的一段\(p[i]=0\),所以要找到这一段里面最小的\(f[i]\)

要是没注意到这两点,就寄了

服了