题目大意: 有n个工厂,每个工厂到第一个工厂的距离为li,每个工厂有pi件货物,现在需要在一些工厂上建设仓库存储货物,费用为costi。 工厂i的物品可以在i~n的工厂内存放,运送到工厂j需要(lj-li)*pi的费用。 求存放所有货物的最小花费。
题目分析: 可以容易的想到动态规划。
设f[i]代表在i位置建立工厂,存放1~i的货物的最小花费。 设sum[i]=∑(1~i)pi (代表前i个工厂的物品和) 设trans[i]=∑(1~i)sum[i-1]*(l[i]-l[i-1]) (代表把所有物品运送至工厂i的花费)
递推公式为: f[i]=min{f[j]+trans[i]-trans[j]-sum[j]*(l[i]-l[j]+cost[i])}
(枚举前面的每一个位置j,j之前的答案为f[j],f[i]的答案即为f[j]加上j到i之间所有货物运送到位置i的费用,不过这里不是重点)
这样做时间复杂度是O(n^2)的,显然时间爆炸。 考虑优化。
假设i位置的答案可由位置j转移。 f[i]=f[j]+trans[i]-trans[j]-sum[j]*(l[i]-l[j]+cost[i])
把括号打开,把和i相关的放到一起,把和j相关的放到一起:
f[i]-trans[i]-cost[i]=f[j]+sum[j]*l[j]-trans[j]-sum[j]*l[i]
设P=f[i]-trans[i]-cost[i] 设y=f[j]+sum[j]*l[j]-trans[j] 设x=sum[j] 设k=l[i] 于是上述式子就转化为: P=y-kx 即y=kx+P,为一次函数形式。 此时除了要求的f[i]与i有关的值都可以视为定值,所以斜率k为定值。 可以将有关j的答案看做是平面上的一些点,我们现在想要求f[i]的最小值,即求P的最小值。 我们可以通过严格的证(hua)明(tu)得知,最优解一定在凸包所代表的点上: 并且观察到最优答案点左侧的斜率小于k,右侧斜率大于k,只要直接找到这个点直接更新答案即可。 观察发现:x是递增的,所以凸包可以线性维护。 观察发现:k是递增的,所以答案点在凸包上也是递增的,所以可以线性查找,直接向右找。 这样更新答案是线性时间复杂度。 总时间复杂度为O(n)。
代码如下:
#include <cstdio> #define N 1200000 using namespace std; typedef long long LL; struct point{ LL x,y; point(LL x=0,LL y=0):x(x),y(y){} LL operator * (const point &c) const { return x*c.y-y*c.x; } point operator - (const point &c) const { return point(x-c.x,y-c.y); } }h[N]; int n; LL l[N],sum[N],trans[N],f[N],cost[N]; int top=0; void convex_hull(point x) { while(top>1 && (h[top]-h[top-1])*(x-h[top-1])<=0) top--; h[++top]=x; return; } bool judge(int now,long long k) { if(now!=top && h[now+1].y-h[now].y<k*(h[now+1].x-h[now].x)) return false; return true; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%lld%lld%lld",&l[i],&sum[i],&cost[i]); sum[i]+=sum[i-1]; trans[i]=trans[i-1]+sum[i-1]*(l[i]-l[i-1]); } int now=1; convex_hull(point(0,0)); for(int i=1;i<=n;i++) { while(!judge(now,l[i])) now++; f[i]=h[now].y-l[i]*h[now].x+trans[i]+cost[i]; convex_hull(point(sum[i],f[i]+sum[i]*l[i]-trans[i])); now=now>top?top:now; } printf("%lld\n",f[n]); return 0; }