( ext{Solution:})
注意到题目中的编号是倒着的,于是我们的距离要预处理的是后缀和。
考虑如何(n^2)搞:
设(dp[i])表示选择(i)为第二个中转点的最小代价。
枚举在(i)前面的(j),代价就是(dp[i]=min_{j<i}All-dis[j]*sum[j]-dis[i]*(sum[i]-sum[j]))
(All)是所有树木运输到(1)号点的代价。可以理解为,有一部分运输到(j)就不用运了,于是把这部分减掉。(sum)是重量的前缀和。
枚举前一个点,显然是(n^2)的(虽然这样可以过)
考虑优化,先推柿子(令(S=All)):
这时,令:(y=dis[j]*sum[j],k=dis[i],x=sum[j],b=(S-dp[i]-dis[i]*sum[i])),一个一次函数式出来了。
显然的斜率优化,但是这里有一个坑,害得我看博客又理解了半天……
其实,如果按照最小化截距来写,会发现这就是一个下凸壳,即使(dis[i])是递减而非递增。但是看了题解后发现,维护的是一个上凸壳。
为什么?
考虑我们究竟要最小化还是最大化。
如果最小化(b=(S-dp[i]-dis[i]*sum[i])),则因为(dp[i])前面的符号是负的,所以我们就反其道而行之地把它给最大化了。于是( ext{Wrong Answer.})
所以,实际上我们要最大化截距(b),从而最小化(dp[i]).
于是我们的任务就变成维护一个上凸包了。观察到(dis[i])递减,于是我们只保留小于(dis[i])的线段。因为后面的最优线段一定是斜率递减的。
我们通过上述分析,可以通过单调队列优化到(O(n).)
这题的主要价值在于,注意(b)的符号,不是题目中要求(min)就一定是下凸包,也不是题目中求最大值就一定是下凸包。看截距的时候要特别留意(dp[i])——我们最关心的值的符号,以此来确定维护上凸壳还是下凸壳。
#include<bits/stdc++.h>
using namespace std;
int n,w[200010],d[200010];
int s[200010],sum[200010];
int dp[200010],ans=0;
int head,tail,q[200010];
int dis[200010],S,A=2147483647;
int Y(int x){return dis[x]*sum[x];}
int X(int x){return sum[x];}
double slope(int x,int y){return (Y(y)-Y(x))/(X(y)-X(x));}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d%d",&w[i],&dis[i]);
for(int i=n;i>=1;--i)dis[i]+=dis[i+1];
for(int i=1;i<=n;++i)ans+=dis[i]*w[i];
for(int i=1;i<=n;++i)sum[i]=sum[i-1]+w[i];
//Dis[i]>(dis[k]*sum[k]-dis[j]*sum[j])/(sum[k]-sum[j])
//Dp[i]=S-dis[j]*sum[j]-dis[i]*(sum[i]-sum[j])
//Dis[j]*sum[j]=dis[i]*sum[j]+(S-dp[i]-dis[i]*sum[i])
//最小化后面一坨 斜率是dis[i]
//head=tail=1;q[head]=1;
//cout<<ans<<endl;
for(int i=1;i<=n;++i){
while(head<tail&&slope(q[head],q[head+1])>=dis[i])head++;
dp[i]=ans-dis[q[head]]*sum[q[head]]-dis[i]*(sum[i]-sum[q[head]]);
//cout<<dp[i]<<" ";
A=min(A,dp[i]);
while(head<tail&&slope(q[tail-1],q[tail])<=slope(q[tail-1],i))--tail;
q[++tail]=i;
}
printf("%d
",A);
return 0;
}