【BZOJ】1096: [ZJOI2007]仓库建设(dp+斜率优化)
http://www.lydsy.com/JudgeOnline/problem.php?id=1096
首先得到dp方程(我竟然自己都每推出了QAQ)$$d[i]=min\{d[j]+cost(j+1,i)\}+c[i]$$其中$d[i]$是前i个且在i建仓库的最小费用,$cost(j+1,i)$是将j+1~i的东西全都运到i的费用
而我们先考虑cost怎么求,orz,好神的前缀和,首先维护sum[i]表示1~i的物品,则j~i的东西从j全都运到i需要$$(sum[i]-sum[j]) \times x[i]$$,然后再设前缀和b[i]表示1~i的物品每一个i从1运到i的费用,则$$cost[j+1,i]=(sum[i]-sum[j]) \times x[i]-(b[i]-b[j])$$其中$$sum[i]=sum[i-1]+p[i], b[i]=b[i-1]+p[i] \times x[i]$$
然后方程变成$$d[i]=min\{d[j]+(sum[i]-sum[j]) \times x[i]-(b[i]-b[j])\}+c[i]$$化简一下可得$$d[i]=min\{d[j]+b[j]-x[i] \times sum[j])\}+c[i]+sum[i] \times x[i]-b[i]$$
而对于这种方程,显然斜率优化。
而斜率优化有几个技巧:
1、不用1D1D直接上斜率,用通俗易懂的方法:
假设决策j<k且k比j优,即:
$$d[k]+b[k]-x[i] \times sum[k]<d[j]+b[j]-x[i] \times sum[j]$$
化简成:
$$\frac{d[k]+b[k]-d[j]-b[j]}{sum[k]-sum[j]}<x[i]$$
因为x[i]单调递增,所以当任意i<L,满足上边不等式,即决策k比j优(在i情况下k比j优的情况时),任意i<L的决策总是大于等于k的,所以当i取最优决策时,i<L的决策单调不降
接下来就是坐标轴的x单调递增,即根据上式,我们可以将点看做(sum[k], d[k]-b[k]),所以可以得到那个斜率方程,这样显然作为坐标轴x的sum[k]是单调不降的,所以得证。
因此这些决策就是二维平面上的点集,想象斜率为x[i]的直线从负无穷向正无穷扫,碰到的第一个点就是最优点(及凸包上的点),理由很简单,因为我们有上边那个不等式,凸包上的点k到任意点j<k的斜率总小于其它不在凸包上的点到任意点的斜率,即k为最优。
那么就维护一个单调队列,斜率递增,然后向求凸包的算法一样在队尾维护凸包即可。
2、论文《1D1D动态规划优化初步》上的做法(其实1的做法是这种做法的简便形式)
而之前的某题:【BZOJ】1010: [HNOI2008]玩具装箱toy(dp+斜率优化)
#include <cstdio> #include <cstring> #include <cmath> #include <string> #include <iostream> #include <algorithm> #include <queue> #include <set> #include <map> using namespace std; typedef long long ll; #define pii pair<int, int> #define mkpii make_pair<int, int> #define pdi pair<double, int> #define mkpdi make_pair<double, int> #define pli pair<ll, int> #define mkpli make_pair<ll, int> #define rep(i, n) for(int i=0; i<(n); ++i) #define for1(i,a,n) for(int i=(a);i<=(n);++i) #define for2(i,a,n) for(int i=(a);i<(n);++i) #define for3(i,a,n) for(int i=(a);i>=(n);--i) #define for4(i,a,n) for(int i=(a);i>(n);--i) #define CC(i,a) memset(i,a,sizeof(i)) #define read(a) a=getint() #define print(a) printf("%d", a) #define dbg(x) cout << (#x) << " = " << (x) << endl #define error(x) (!(x)?puts("error"):0) #define printarr2(a, b, c) for1(_, 1, b) { for1(__, 1, c) cout << a[_][__]; cout << endl; } #define printarr1(a, b) for1(_, 1, b) cout << a[_] << '\t'; cout << endl inline const int getint() { int r=0, k=1; char c=getchar(); for(; c<'0'||c>'9'; c=getchar()) if(c=='-') k=-1; for(; c>='0'&&c<='9'; c=getchar()) r=r*10+c-'0'; return k*r; } inline const int max(const int &a, const int &b) { return a>b?a:b; } inline const int min(const int &a, const int &b) { return a<b?a:b; } const int N=1000005; int n, q[N], front, tail; ll x[N], p[N], c[N], sum[N], b[N], f[N]; #define F(x) (f[x]+b[x]) #define E(k,j) (F(k)-F(j)) #define S(k,j) (sum[k]-sum[j]) int main() { read(n); for1(i, 1, n) read(x[i]), read(p[i]), read(c[i]); for1(i, 1, n) sum[i]=sum[i-1]+p[i]; for1(i, 1, n) b[i]=b[i-1]+p[i]*x[i]; tail=1; for1(i, 1, n) { while(front<tail-1 && E(q[front+1], q[front])<x[i]*S(q[front+1], q[front])) ++front; int j=q[front]; f[i]=f[j]+(sum[i]-sum[j])*x[i]-(b[i]-b[j])+c[i]; while(front<tail-1 && E(q[tail-1], q[tail-2])*S(i, q[tail-1])>E(i, q[tail-1])*S(q[tail-1], q[tail-2])) --tail; q[tail++]=i; } printf("%lld", f[n]); return 0; }
Description
L公司有N个工厂,由高到底分布在一座山上。如图所示,工厂1在山顶,工厂N在山脚。 由于这座山处于高原内陆地区(干燥少雨),L公司一般把产品直接堆放在露天,以节省费用。突然有一天,L公司的总裁L先生接到气象部门的电话,被告知三天之后将有一场暴雨,于是L先生决定紧急在某些工厂建立一些仓库以免产品被淋坏。由于地形的不同,在不同工厂建立仓库的费用可能是不同的。第i个工厂目前已有成品Pi件,在第i个工厂位置建立仓库的费用是Ci。对于没有建立仓库的工厂,其产品应被运往其他的仓库进行储藏,而由于L公司产品的对外销售处设置在山脚的工厂N,故产品只能往山下运(即只能运往编号更大的工厂的仓库),当然运送产品也是需要费用的,假设一件产品运送1个单位距离的费用是1。假设建立的仓库容量都都是足够大的,可以容下所有的产品。你将得到以下数据: 工厂i距离工厂1的距离Xi(其中X1=0); 工厂i目前已有成品数量Pi; 在工厂i建立仓库的费用Ci; 请你帮助L公司寻找一个仓库建设的方案,使得总的费用(建造费用+运输费用)最小。
Input
第一行包含一个整数N,表示工厂的个数。接下来N行每行包含两个整数Xi, Pi, Ci, 意义如题中所述。
Output
仅包含一个整数,为可以找到最优方案的费用。
Sample Input
0 5 10
5 3 100
9 6 10
Sample Output
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位带符号整数。