斜率优化 [ZJOI2007] 仓库建设
[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\)所有商品都运到仓库里面的最小代价
麻烦就麻烦在这个sumval怎么快速计算
如果是O(n2)的算法,那这个直接预处理就行,也是O(n2)
但是O(n^2)不能接受,这个状态已经是十分简洁了,没有什么优化的空间,我们要优化,首先就要把这个sumval拆开。
所以最终的方程就是
然后式子里面有\(cnt[i] \times prex[k]\) 这明显是斜率优化的形式
那就按照斜率优化化简子吧
很明显的式子,\(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]\)
要是没注意到这两点,就寄了
服了