[斜率优化DP] BZOJ1096: [ZJOI2007]仓库建设

来源:互联网 发布:c语言数据类型字节数 编辑:程序博客网 时间:2024/06/01 08:08

题意

有N个工厂按编号顺序排成一条直线,第1个工厂到第i个工厂的距离为dis[i], 第i个工厂一开始堆积着p[i]个货物,现在要紧急把所有货物装进仓库里。在第i个工厂建仓库需要c[i]的代价,对于没有建立仓库的工厂,其货物应被运往其他的仓库进行储藏,且只能往编号大仓库的运,一件货物运送1个单位距离的代价是1。求把所有货物装进仓库的最小代价。
(n<=100000)

题解

显然是DP,要用到斜率优化。
很容易设计出O(n^2)的算法,f[i]表示前i个工厂送完的最优解。
转移方程:f[i]=c[i]+min{ f[j]+ ix=j+1p[x](dis[i]dis[x]) }
先把方程进行整理:
f[i]=c[i]+min{ f[j]+ dis[i]ix=j+1p[x]ix=j+1p[x]dis[x]) }
写成前缀和的形式,设sum_p[i]为 p[i]的前缀和,sum_pd[i]为p[i]*dis[i]的前缀和。
f[i]=c[i]+min{ f[j]+dis[i]*( sum_p[i] - sum_p[j] )+ sum_pd[i] - sum_pd[j] }
f[i]=c[i] + dis[i]*sum_p[i] + sum_pd[i] + min{ f[j] - sum_pd[j] - dis[i]*sum_p[j] }
我们现在只需关注min中的内容,设 M = f[j] - sum_pd[j] - dis[i]*sum_p[j]
整理得 dis[i]*sum_p[j] + M = f[j] - sum_pd[j]
直线的方程 k * x + b = y
我们把一个决策j转化成了点(sum_p[j], f[j] - sum_pd[j])
那么找当前状态i的最小的M转化为:将一条斜率为dis[i]的直线从下向上移动,碰到第一个点就是最优决策。因为这时直线的截距b最小,对应M最小。
显然能成为最优决策的点一定是凸包(下凸壳)上的点。
因为点的x坐标有单调性,新点只在末端插入,所以可以用一个栈来维护凸壳(Graham)。
又因为斜率dis[i]是单调的,所以决策点也是单调的,即随着i的增加在凸壳上往右动。所以找最优点时只需记一个决策指针在栈中一步步推就行了。
因为有决策单调和x单调这两个很好的性质,复杂度可优化为O(n)。

#include<cstdio>#include<algorithm>typedef long long LL;using namespace std;const int maxn=1000005;int n;LL f[maxn],d[maxn],p[maxn],c[maxn],sum_p[maxn],sum_pd[maxn];struct Point{    LL x,y;    Point(LL x=0,LL y=0):x(x),y(y){}} que[maxn];typedef Point Vector;Vector operator - (Point A,Point B){ return Vector(A.x-B.x,A.y-B.y); }LL Cross(Vector A,Vector B){ return A.x*B.y-A.y*B.x; }LL getM(int now,int k){    return que[now].y-que[now].x*k;}int main(){    scanf("%d",&n);    for(int i=1;i<=n;i++){        scanf("%lld%lld%lld",&d[i],&p[i],&c[i]);        sum_p[i]=sum_p[i-1]+p[i];        sum_pd[i]=sum_pd[i-1]+p[i]*d[i];    }    while(n&&!p[n]) n--;    que[0].x=que[0].y=0;    int head=0,tail=0;    for(int i=1;i<=n;i++){        while(head<tail&&getM(head,d[i])>getM(head+1,d[i])) head++;        f[i]=getM(head,d[i])+c[i]+d[i]*sum_p[i]-sum_pd[i];        Point now(sum_p[i],f[i]+sum_pd[i]);        while(tail>0&&Cross(que[tail]-que[tail-1],now-que[tail])<0) tail--;        que[++tail]=now;        if(head>tail) head=tail;    }    printf("%lld\n",f[n]);    return 0;} 
0 0
原创粉丝点击