锯木厂选址

https://loj.ac/problem/10192

题目描述

  从山顶上到山底下沿着一条直线种植了\(n\)棵老树。当地的政府决定把他们砍下来。为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂。木材只能朝山下运。山脚下有一个锯木厂。另外两个锯木厂将新修建在山路上。你必须决定在哪里修建这两个锯木厂,使得运输的费用总和最小。假定运输每公斤木材每米需要一分钱。计算最小运输费用。

思路

  由于只能建\(2\)个锯木厂,所以我们只需要用\(f[i][0/1]\)记录到\(i\)为止建\(1/2\)锯木厂的最小代价。

  我们记\(cost(i,j)\)\(i+1\)\(j\)这段的树木全部运往\(j\)的代价,令\(d[i]\)为距离前缀和,\(sumw[i]\)为重量前缀和,\(s[i]\)为质量乘距离的前缀和,那么

\[cost(i,j)=\sum_{k=i+1}^j w[k]*(d[j]-d[k])=d[j]*(sumw[j]-sumw[i])-s[j]+s[i] \]

  所以转移方程为

\[f[i][0]=cost(0,i) f[i][1]=f[j][0]+cost(j,i)+cost(i,n+1) \]

  我们只要对\(f[j][0]\)维护一下最小值即可,用斜率优化。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=2e5+10;

ll read()
{
	ll res=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){res=(res<<3)+(res<<1)+(ch^48);ch=getchar();}
	return res*w;
}

ll d[N],s[N],sumw[N],f[N][2],w[N],q[N];
ll cost(ll j,ll i)
{
	return d[i]*(sumw[i]-sumw[j])-s[i]+s[j];
}
int main()
{
	ll n=read();
	for(ll i=1;i<=n;i++)
	{
		ll x=read(),y=read();
		d[i+1]=d[i]+y;
		sumw[i]=sumw[i-1]+x;
		s[i]=s[i-1]+d[i]*x;
	}
	sumw[n+1]=sumw[n];s[n+1]=s[n];
	ll l=0,r=0;
	for(ll i=1;i<=n;i++)
	{
		f[i][0]=cost(0,i);
		if(i==1){q[++r]=i;continue ;}
		while(l<r&&(f[q[l+1]][0]+s[q[l+1]]-f[q[l]][0]-s[q[l]])<=d[i]*(sumw[q[l+1]]-sumw[q[l]]))l++;
		f[i][1]=f[q[l]][0]+cost(q[l],i)+cost(i,n+1);
		while(l<r&&(f[q[r]][0]+s[q[r]]-f[q[r-1]][0]-s[q[r-1]])*(sumw[i]-sumw[q[r]])
					>=(f[i][0]+s[i]-f[q[r]][0]-s[q[r]])*(sumw[q[r]]-sumw[q[r-1]]))r--;
		q[++r]=i;
//		for(int j=1;j<i;j++)
//			f[i][1]=min(f[i][1],f[j][0]+cost(j,i)+cost(i,n+1));
	}
	ll ans=(1ll<<60);
	for(ll i=2;i<=n;i++)
		ans=min(ans,f[i][1]);
	printf("%lld",ans);
}
posted @ 2019-11-13 22:07  fbz  阅读(199)  评论(0编辑  收藏  举报