Problem
从山顶上到山底下沿着一条直线种植了 n n n 棵老树。当地的政府决定把他们砍下来。为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂。
木材只能朝山下运。山脚下有一个锯木厂。另外两个锯木厂将新修建在山路上。你必须决定在哪里修建这两个锯木厂,使得运输的费用总和最小。假定运输每公斤木材每米需要一分钱。
你的任务是编写一个程序,从输入文件中读入树的个数和他们的重量与位置,计算最小运输费用。
数据范围: 2 ≤ n ≤ 20000 2≤n≤20000 2≤n≤20000。
Solution
我们定义 S i S_i Si 为 w i w_i wi 的前缀和, c o s t cost cost 为 w i w_i wi 的总和, d i s i dis_i disi 为 d i d_i di 的后缀和, f i f_i fi 为以 i i i 作为第二个锯木厂的最小花费。
那么显然有如下转移:
f i = min j = 1 i − 1 { c o s t − S j × d i s j − ( S i − S j ) × d i s i } f_i=\min_{j=1}^{i-1}\{cost-S_j\times dis_j-(S_i-S_j)\times dis_i\} fi=j=1mini−1{cost−Sj×disj−(Si−Sj)×disi}
就相当于是枚举第一个锯木厂然后考虑省掉的花费。
然后这明显是可以斜率优化的,按照套路来就可以了。
最后化出来,当 k < j < i k<j<i k<j<i 且 j j j 比 k k k 优时,有:
S j × d i s j − S k × d i s k S j − S k > d i s i \frac{S_j\times dis_j-S_k\times dis_k}{S_j-S_k}>dis_i Sj−SkSj×disj−Sk×disk>disi
由于 d i s i dis_i disi 是递减的,我们可以维护一个上凸壳转移。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 20005
using namespace std;
int n,w[N],d[N],S[N],dis[N],Q[N];
double slope(int x,int y) {return 1.0*(S[y]*dis[y]-S[x]*dis[x])/(S[y]-S[x]);}
int main(){
scanf("%d",&n);
int Cost=0;
for(int i=1;i<=n;++i) scanf("%d%d",&w[i],&d[i]);
for(int i=n;i>=1;--i) dis[i]=dis[i+1]+d[i];
for(int i=1;i<=n;++i) S[i]=S[i-1]+w[i],Cost+=w[i]*dis[i];
int l=0,r=0,ans=2e9+1;
for(int i=1;i<=n;++i){
while(l<r&&slope(Q[l],Q[l+1])>=dis[i]) l++;
ans=min(ans,Cost-S[Q[l]]*dis[Q[l]]-(S[i]-S[Q[l]])*dis[i]);
while(l<r&&slope(Q[r-1],Q[r])<=slope(Q[r],i)) r--;
Q[++r]=i;
}
printf("%d\n",ans);
return 0;
}