链接:
题意:
有 \(n\) 个点依次编号为 \(1\sim n\)。给出这 \(n\) 个点的信息,包括位置 \(x_i\),所拥有的的物品数量 \(p_i\),在此建设一个仓库的费用 \(c_i\)。
每个物品可以向编号更大的点移动,一个物品移动一个单位距离的费用为1。
求将所有物品都放进仓库所需的最小费用。
分析:
我们可以比较容易地想出一个 \(dp[i]\) 表示在第 \(i\) 个点建一个仓库时的最优费用,那么枚举上一个仓库的位置,有
\[dp[i]=dp[j]+\sum_{k=j+1}^i(x_i-x_k)\cdot p_k+c_i \]把 \(\sum\) 拆开,有
\[dp[i]=dp[j]+x_i\sum_{k=j+1}^ip_k-\sum_{k=j+1}^i(x_k\cdot p_k)+c_i \]于是设 \(sum1[i]=\sum\limits_{k=1}^ip_k\),\(sum2[i]=\sum\limits_{k=1}^i(x_k\cdot p_k)\),可以预处理出来。
\[dp[i]=dp[j]+x_isum1[i]-x_isum1[j]-sum2[i]+sum2[j]+c_i \]发现有 \(x_isum1[j]\) 这种既与当前位置有关,又与决策有关的项,于是按照斜率优化的套路,我们把式子化成直线表达式的形式:
\[(sum2[j]+dp[j])=x_isum1[j]+(dp[i]-x_isum1[i]+sum2[i]-c_i) \]于是我们只需要最小化斜率为 \(x_i\),过点 \((sum1[j],sum2[j]+dp[j])\) 的直线的截距。
发现和玩具装箱那题很像,也是维护一个下凸包,同样有斜率 \(x_i\) 递增,同样用单调队列优化,于是就可以 \(O(n)\) 了。
算法:
单调队列维护下凸包,同时维护最优决策点,然后每次根据最优决策的信息得到 \(dp[i]\),继续维护凸包即可。时间复杂度 \(O(n)\)。
细节1:
这个细节是仅关于本题的,也是洛谷上的 hack
数据。
我们对 dp 的定义是在第 \(i\) 个位置建一个仓库时的最优费用,于是如果 dp 完直接输出 dp[n]
是有问题的。当最后连续多个点根本没有物品时,最后一个仓库可能会建在最后一个有物品的点和最后一个点及其之间的所有点上,所以我们最后还要取一个 min
。
细节2:
另外,对于决策点横坐标相等的问题,由于本题数据弱放了过去,所以我会在 [SDOI2012]任务安排 的题解中详细说明这个问题。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
inline int read(){
int p=0,f=1;
char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){p=p*10+c-'0';c=getchar();}
return p*f;
}
#define X(x) (sum1[x])
#define Y(x) (sum2[x]+dp[x])
#define dx(x,y) (X(x)-X(y))
#define dy(x,y) (Y(x)-Y(y))
#define slope(x,y) ((double)dy(x,y)/dx(x,y))
const int N=1e6+5;
const int inf=0x7fffffffffffffff;
int n,x[N],p[N],c[N],dp[N],sum1[N],sum2[N],q[N],qh,qt;
signed main(){
n=in;
for(int i=1;i<=n;i++)
x[i]=in,p[i]=in,c[i]=in,
sum1[i]=sum1[i-1]+p[i],
sum2[i]=sum2[i-1]+x[i]*p[i];
qh=qt=1;
for(int i=1;i<=n;i++){
while(qh<qt&&slope(q[qh+1],q[qh])<x[i])qh++;
dp[i]=Y(q[qh])-x[i]*X(q[qh])+x[i]*sum1[i]-sum2[i]+c[i];
while(qh<qt&&slope(q[qt],q[qt-1])>=slope(i,q[qt-1]))qt--;
q[++qt]=i;
}
int ans=dp[n];
while(!p[n])ans=min(ans,dp[--n]);
cout<<ans;
return 0;
}