斜率优化 【ZJOI 2007】仓库建设 bzoj1096

来源:互联网 发布:英文小说优美 知乎 编辑:程序博客网 时间:2024/05/21 17:42

题目大意:
有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 1200000using 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;}
0 0
原创粉丝点击