P2120 [ZJOI2007] 仓库建设

题目大意

\(n\) 个工厂,每个工厂有 \(p_i\) 的货物,货物运输一个单位距离的费用是 \(1\),工厂可以建造仓库,费用为 \(c_i\)。工厂与工厂 \(1\) 的距离为 \(x_i\)。要求将货物运送到下一个最近的仓库,求最小费用。

\(1\leq n\leq 10^6\)

思路

先考虑最基本的动规:
\(f_i\) 表示在这里建仓库时 \([1,i]\) 的转移与建造的最小费用。则有方程:

\[f_i=\min(f_j+\sum_{k=j+1}^i(x_i-x_k)p_k)+c_i) \]

化简:

\[f_i=\min(f_j+\sum_{k=j+1}^ix_ip_k-\sum_{k=j+1}^ix_kp_k+c_i) \]

\[f_i=\min(f_j+x_i\sum_{k=j+1}^ip_k-\sum_{k=j+1}^ix_kp_k+c_i) \]

\(s_x=\sum_{i=1}^x p_i\)\(v_x=\sum_{i=1}^x x_ip_i\),则原式化为:

\[f_i=\min(f_j+x_i(s_i-s_j)-(v_i-v_j)+c_i) \]

\[f_i=\min(f_j+x_is_i-x_is_j-v_i+v_j+c_i) \]

整理一下就是设函数 \(F_k(x)=-s_kx+f_k+v_k\)\(C_k=s_kx_k-v_k+c_k\) 则原式就转化成了斜率优化可以做的样子:

\[f_i=\min(F_j(x_i)+C_i) \]

对于要求的 \(f_i\),维护 \(F_k\) 其中 \(k\in[0,i)\)。之后找到一个最优的 \(F_k\) 使得 \(F_k(x_i)\) 能取到最低点。换句话说我们维护的是一个凸包。像
这里的紫色部分显然就是答案。之后考虑维护。


观察到 \(s\) 严格单调递增,即 \(F_k\) 的斜率 \(-s_k\) 严格单调下降,即函数不够优秀以后不再会选择。考虑将函数维护成单调队列。发现凸包会被交点分成一段一段,则只要删除的时候发现交点大于 \(x_i\) 就可以舍去。则之后的 \(f_i\) 可以设成 \(f_i=F_{\text{front}}(x_i)+C_i\),其中 \(\text{front}\) 指单调队列队首。然后考虑插入 \(F_i\)。可以直接用当前的最后一个交点与次大函数与 \(F_i\) 的交点进行比较,如果小的话就可以继续出队列,直到更新不好为止,之后插入 \(F_i\)

求函数交点很好求。对于 \(y_0=kx+b\)\(y_1=px+q\) 来说它们的交点就是

\[kx+b=px+q \]

\[kx-px=q-b \]

\[x=\frac{q-b}{k-p} \]

特别的,在插入函数时如果它们的斜率一样就可以直接取截距小的那个,因为它们平行(或重合),当然取截距小的。

代码

#include<bits/stdc++.h>
#define v(u,x) (u.a*1.0*x+u.b)
using namespace std;
typedef long long ll;
typedef long double ld;
const ll MAXN=1e6+5;
struct Func{
	ld a,b;
	Func(ld _a,ld _b){
		a=_a;
		b=_b;
	}
};
ld jd(Func p,Func q){
	if(p.a==q.a){
		if(p.b>=q.b){
			return 1e18;
		} 
		return -1e18;
	}
	return (q.b-p.b)*1.0/(p.a-q.a);
}
ll sxp[MAXN],sp[MAXN];
ll n;
ll x[MAXN],p[MAXN],c[MAXN]; 
ll f[MAXN];
deque<Func>dq;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>x[i]>>p[i]>>c[i];
		sp[i]=sp[i-1]+p[i];
		sxp[i]=sxp[i-1]+x[i]*p[i];
	}
	dq.push_back(Func(0,0));
	for(int i=1;i<=n;++i){
		while(dq.size()>1){
			Func F=dq.front();
			dq.pop_front();
			if(jd(dq.front(),F)>x[i]){
				dq.push_front(F);
				break;
			}
		}
		f[i]=v(dq.front(),x[i])+sp[i]*x[i]-sxp[i]+c[i];
		Func nw=Func(-sp[i],f[i]+sxp[i]);
		while(dq.size()>1){
			Func F=dq.back();
			dq.pop_back();
			if(jd(dq.back(),F)<jd(dq.back(),nw)){
				dq.push_back(F);
				break;
			}
		}
		dq.push_back(nw);
	}
	ll ans=1e18;
	for(int i=n;i>=1;--i){
		ans=min(ans,f[i]);
		if(p[i]){
			cout<<ans<<endl;
			return 0;
		}
	}
	return 0;
} 

热门相关:超级吸收   神话版三国   我的绝色美女房客   呆萌小青梅:妖孽竹马太腹黑   我的全世界就是你