bzoj1096[ZJOI2007]仓库建设——DP+斜率优化

Description
  L公司有N个工厂,由高到底分布在一座山上。如图所示,工厂1在山顶,工厂N在山脚。由于这座山处于高原内陆地区(干燥少雨),L公司一般把产品直接堆放在露天,以节省费用。突然有一天,L公司的总裁L先生接到气象部门的电话,被告知三天之后将有一场暴雨,于是L先生决定紧急在某些工厂建立一些仓库以免产品被淋坏。由于地形的不同,在不同工厂建立仓库的费用可能是不同的。第i个工厂目前已有成品Pi件,在第i个工厂位置建立仓库的费用是Ci。对于没有建立仓库的工厂,其产品应被运往其他的仓库进行储藏,而由于L公司产品的对外销售处设置在山脚的工厂N,故产品只能往山下运(即只能运往编号更大的工厂的仓库),当然运送产品也是需要费用的,假设一件产品运送1个单位距离的费用是1。假设建立的仓库容量都都是足够大的,可以容下所有的产品。你将得到以下数据:
1:工厂i距离工厂1的距离Xi(其中X1=0);
2:工厂i目前已有成品数量Pi;
3:在工厂i建立仓库的费用
Ci;请你帮助L公司寻找一个仓库建设的方案,使得总的费用(建造费用+运输费用)最小。

Input
  第一行包含一个整数N,表示工厂的个数。接下来N行每行包含两个整数Xi, Pi, Ci, 意义如题中所述。

Output
  仅包含一个整数,为可以找到最优方案的费用。

Sample Input
3

0 5 10

5 3 100

9 6 10
Sample Output
32
HINT
在工厂1和工厂3建立仓库,建立费用为10+10=20,运输费用为(9-5)*3 = 12,总费用32。如果仅在工厂3建立仓库,建立费用为10,运输费用为(9-0)*5+(9-5)*3=57,总费用67,不如前者优。

【数据规模】

对于100%的数据, N ≤1000000。 所有的Xi, Pi, Ci均在32位带符号整数以内,保证中间计算结果不超过64位带符号整数。


这道题肯定是DP,而且DP状态转移方程也十分容易推出,我们设DP[i]表示在i建立工厂时,编号小于i的点货物全部入仓的最小费用,则方程为:
dp[i]=min(dp[i],dp[j]+f[i].c+f[i].x*(a[i]-a[j])-(s[i]-s[j]));
/*这是改进过的方程,原方程为:
dp[i]=min(dp[i],dp[j]+f[i].c+∑(f[i].x-f[j].x)*f[j].p) (1<=j<i)
我们用前缀和优化∑部分,令s[i]=f[1].x*f[1].p+f[2].x*f[2].p+...+f[i].x*f[i].p
令a[i]=f[1].p+f[2].p+...+f[i].p,即可推导出优化的方程
*/
接下去的操作就是用斜率来优化了,我们设存在j使dp[i]最小,则
dp[i]=dp[j]+f[i].c+f[i].x*(a[i]-a[j])-(s[i]-s[j])
dp[i]=dp[j]+f[i].c+f[i].x*a[i]-f[i].x*a[j]-s[i]+s[j]
dp[i]+f[i].x*a[j]=dp[j]+f[i].c+f[i].x*a[i]-s[i]+s[j]  //移项
// b  +  k  *  x =      y
//所以我们可以将dp[i]看为截距,f[i].x看为斜率,j点坐标为(a[j],dp[j]+f[i].c+f[i].x*a[i]-s[i]+s[j])
因为斜率k(f[i].x)随i单调递增,所以我们可以用单调队列维护一个下凸包,来求截距b(dp[i])
关于维护单调队列来维护下凸包,我写过一遍blog,不懂得可以点这里

Code:

#include<bits/stdc++.h>
#define MAXN 1000005
#define ll long long
using namespace std;
ll read(){
    char c;ll x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
struct node{
    ll x,p,c;
}f[MAXN];
ll n,r,w,dp[MAXN],s[MAXN],a[MAXN],q[MAXN];
double x(int i){return a[i];}
double y(int i){return dp[i]+s[i];}
double k(int i,int j){return (y(j)-y(i))/(x(j)-x(i));}
int main()
{
    n=read();
    register int i;
    for(i=1;i<=n;i++){
        f[i]=(node){read(),read(),read()};
        s[i]=s[i-1]+f[i].x*f[i].p;
        a[i]=a[i-1]+f[i].p;
        dp[i]=1e18;
    }
    r=w=0;
    for(i=1;i<=n;i++){
        while(r<w&&f[i].x>k(q[r],q[r+1])) r++;
        ll j=q[r];dp[i]=dp[j]+f[i].c+f[i].x*a[i]-f[i].x*a[j]-s[i]+s[j];
        while(r<w&&k(q[w],i)<k(q[w-1],q[w])) w--;
        q[++w]=i;
    }
    printf("%lld",dp[n]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值