【CEOI 2004】锯木厂选址

传送门


Problem

从山顶上到山底下沿着一条直线种植了 n n n 棵老树。当地的政府决定把他们砍下来。为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂。

木材只能朝山下运。山脚下有一个锯木厂。另外两个锯木厂将新修建在山路上。你必须决定在哪里修建这两个锯木厂,使得运输的费用总和最小。假定运输每公斤木材每米需要一分钱。

你的任务是编写一个程序,从输入文件中读入树的个数和他们的重量与位置,计算最小运输费用。

数据范围: 2 ≤ n ≤ 20000 2≤n≤20000 2n20000


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=1mini1{costSj×disj(SiSj)×disi}

就相当于是枚举第一个锯木厂然后考虑省掉的花费。

然后这明显是可以斜率优化的,按照套路来就可以了。

最后化出来,当 k &lt; j &lt; i k&lt;j&lt;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 &gt; d i s i \frac{S_j\times dis_j-S_k\times dis_k}{S_j-S_k}&gt;dis_i SjSkSj×disjSk×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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值