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

本文介绍了一种结合动态规划与凸包优化技术解决特定仓储问题的方法。通过数学推导和算法实现,将时间复杂度从O(n^2)优化至O(n),并附带完整代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:
有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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值