(贪心、斜率优化)洛谷 P2120 ZJOI2007 仓库建设 题解

题意

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

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

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

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

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

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

1 ≤ n ≤ 1 0 6 1 \leq n \leq 10^6 1n106 0 ≤ x i , p i , c i < 2 31 0 \leq x_i,p_i,c_i < 2^{31} 0xi,pi,ci<231 ∀ 1 ≤ i < n , x i < x i + 1 \forall1 \leq i < n,x_i<x_{i+1} ∀1i<n,xi<xi+1

设答案为 a n s ans ans,保证 a n s + ∑ i = 1 n p i x i < 2 63 ans + \sum\limits_{i = 1}^{n} p_ix_i < 2^{63} ans+i=1npixi<263

思路

考虑朴素的 dp,设 f i f_i fi 表示工厂 i i i 建仓库,前面 i − 1 i-1 i1 个工厂处理完成的最小费用。那么有:
f i = min ⁡ j = 1 i − 1 { f j + c i + ∑ k = j + 1 i p k ( x i − x k ) } f_i=\min_{j=1}^{i-1}\left \{ f_j+c_i+\sum_{k=j+1}^{i}p_k(x_i-x_k)\right \} fi=j=1mini1 fj+ci+k=j+1ipk(xixk)

考虑把求和那一坨变得好看些,设:
s 1 j = ∑ k = 1 j p k x k , s 2 j = ∑ k = 1 j p k s1_j=\sum_{k=1}^{j}p_kx_k,s2_j=\sum_{k=1}^{j}p_k s1j=k=1jpkxk,s2j=k=1jpk

那么变形一下原来的式子:
f j + c i + x i ∑ k = j + 1 i p k − ∑ k = j + 1 i p k x k f_j+c_i+x_i\sum_{k=j+1}^{i}p_k-\sum_{k=j+1}^{i}p_kx_k fj+ci+xik=j+1ipkk=j+1ipkxk

f j + c i + x i ( s 2 i − s 2 j ) − ( s 1 i − s 1 j ) f_j+c_i+x_i(s2_i-s2_j)-(s1_i-s1_j) fj+ci+xi(s2is2j)(s1is1j)

设两个决策点 j 1 < j 2 j1<j2 j1<j2 j 2 j2 j2 优于 j 1 j1 j1,那么:
f j 1 + c i + x i ( s 2 i − s 2 j 1 ) − ( s 1 i − s 1 j 1 ) > f j 2 + c i + x i ( s 2 i − s 2 j 2 ) − ( s 1 i − s 1 j 2 ) f_{j1}+c_i+x_i(s2_i-s2_{j1})-(s1_i-s1_j1)>f_{j2}+c_i+x_i(s2_i-s2_{j2})-(s1_i-s1_{j2}) fj1+ci+xi(s2is2j1)(s1is1j1)>fj2+ci+xi(s2is2j2)(s1is1j2)

x i ( s 2 j 2 − s 2 j 1 ) > f j 2 − f j 1 + s 1 j 2 − s 1 j 1 x_i(s2_{j2}-s2_{j1})>f_{j2}-f_{j1}+s1_{j2}-s1_{j1} xi(s2j2s2j1)>fj2fj1+s1j2s1j1

因为 s 2 j 2 > s 2 j 1 s2_{j2}>s2_{j1} s2j2>s2j1,所以直接除过去:
x i > f j 2 − f j 1 + s 1 j 2 − s 1 j 1 s 2 j 2 − s 2 j 1 x_i>\frac{f_{j2}-f_{j1}+s1_{j2}-s1_{j1}}{s2_{j2}-s2_{j1}} xi>s2j2s2j1fj2fj1+s1j2s1j1

f j 2 − f j 1 + s 1 j 2 − s 1 j 1 s 2 j 2 − s 2 j 1 < x i \frac{f_{j2}-f_{j1}+s1_{j2}-s1_{j1}}{s2_{j2}-s2_{j1}}<x_i s2j2s2j1fj2fj1+s1j2s1j1<xi

s l o p e < x i slope<x_i slope<xi x i x_i xi 单调递增,那么和玩具装箱是同一类,维护斜率单调递增,新建决策点扔到队首。再看看那一张结论图:
在这里插入图片描述
因为最终“集中地”不一定在 n n n,即可能 p n = 0 p_n=0 pn=0,设 p i ≠ 0 , i = i m a x ∈ [ 1 , n ] p_i\ne0,i=imax\in[1,n] pi=0,i=imax[1,n],那么应当计算 [ i , n ] [i,n] [i,n] 的答案,找费用最少得“集中地”。
a n s = min ⁡ j = i n f j ans=\min_{j=i}^{n}f_j ans=j=iminnfj

还要注意一点就是有精度问题,因为这个在洛谷被卡了很久,因此把斜率式子的分子分母拆开来交叉相乘进行对比。具体细节建代码。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define dd double
#define ls u<<1
#define rs u<<1|1
const ll N=1e6+9,inf=0x7f7f7f7f;
ll n,x[N],p[N],c[N];
ll s1[N],s2[N];
ll q[N],f[N];
ll slope_up(ll j1,ll j2)
{
	return f[j2]-f[j1]+s1[j2]-s1[j1];
}
ll slope_down(ll j1,ll j2)
{
	return s2[j2]-s2[j1];
}
/*dd slope(ll j1,ll j2)
{
	if(s2[j2]==s2[j1])return inf;
	return (dd)(f[j2]-f[j1]+s1[j2]-s1[j1])/(dd)(s2[j2]-s2[j1]);
}惨死现场,被#13卡*/
int main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld%lld%lld",&x[i],&p[i],&c[i]);
		s1[i]=s1[i-1]+x[i]*p[i];
		s2[i]=s2[i-1]+p[i];
	}
	ll hh=0,tt=0;
	for(int i=1;i<=n;i++)
	{
		while(hh<tt&&slope_up(q[hh],q[hh+1])<x[i]*slope_down(q[hh],q[hh+1]))hh++;
		f[i]=f[q[hh]]+c[i]-(s1[i]-s1[q[hh]])+x[i]*(s2[i]-s2[q[hh]]);
		while(hh<tt&&slope_up(q[tt],i)*slope_down(q[tt-1],q[tt])<slope_up(q[tt-1],q[tt])*slope_down(q[tt],i))tt--;
		q[++tt]=i;
	}
	ll i=n,ans=f[n];
	while(i&&p[i]==0)
	{
		i--;
		ans=min(ans,f[i]);
	}
	printf("%lld",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值