Description
从山顶上到山底下沿着一条直线种植了n棵老树。当地的政府决定把他们砍下来。为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂。木材只能按照一个方向运输:朝山下运。山脚下有一个锯木厂。另外两个锯木厂将新修建在山路上。你必须决定在哪里修建两个锯木厂,使得传输的费用总和最小。假定运输每公斤木材每米需要一分钱。任务你的任务是写一个程序:从标准输入读入树的个数和他们的重量与位置计算最小运输费用将计算结果输出到标准输出(2≤n≤20 000)
Solution
设\(S[i]\)为重量前缀和,\(Sd[i]\)为距离前缀和,\(d[i]\)为第\(i\)棵树到第\(i+1\)棵树的距离
那么第一个锯木厂费用:\(Cost[i]=Cost[i-1]+S[i-1]*d[i-1]\)
而到第二个锯木厂费用:\(W(i,j)=Cost[j]-Cost[i-1]-S[i-1]*(Sd[j]-Sd[i-1])\)
那么\(Ans=min\{Cost[j]+W(j+1,i)+W(i+1,n+1)\}\)
乱搞一下发现斜率式,此时\(i>k>j\),且\(k\)比\(j\) 优
\(\frac{S[j]*Sd[j]-S[k]*Sd[k]}{S[j]-S[k]}<Sd[i]\)
然后就完了
Tips:
? 由于\(S[k]>S[j]\)所以\(S[j]-S[k]<0\) 把斜率式变形的时候记得变符号
? 因为不知道在哪设锯木厂最优,用一个\(Ans\)变量随时更新,否则WA
? 虽然答案在int范围,但是如果化除为乘的话中间结果要long long
Code
#include <cstdio>
#include <algorithm>
#define N 20010
using namespace std;
int n,d[N],s[N],sd[N],cost[N],Ans;
int l,r,q[N];
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline void Init(){
n=read();
for(int i=1;i<=n;++i){
s[i]=s[i-1]+read(),d[i]=read();
sd[i]=sd[i-1]+d[i-1];
cost[i]=cost[i-1]+d[i-1]*s[i-1];
}
s[n+1]=s[n];
sd[n+1]=sd[n]+d[n];
cost[n+1]=cost[n]+d[n]*s[n];
}
inline int f(int j,int k){return s[j]*sd[j]-s[k]*sd[k];}
inline int g(int j,int k){return s[j]-s[k];}
int h(int i,int j){return cost[n+1]-s[j]*(sd[i]-sd[j])-s[i]*(sd[n+1]-sd[i]);}
inline void DP(){
l=r=1;Ans=1e9;
for(int i=1;i<=n;++i){
while(l<r&&f(q[l],q[l+1])>sd[i]*1ll*g(q[l],q[l+1])) l++;
int j=q[l];
Ans=min(Ans,h(i,j));
while(l<r&&f(q[r],i)*1ll*g(q[r-1],q[r])<g(q[r],i)*1ll*f(q[r-1],q[r])) r--;
q[++r]=i;
}
}
int main(){
Init();
DP();
printf("%d\n",Ans);
return 0;
}