【BZOJ】【1096】【ZJOI2007】仓库建设
DP/斜率优化
Orz Hzwer
八中好像挂了……明天再提交吧……
UPD:2015-03-12 17:24:43
算了,毕竟是第一道题,还是仔细写一下斜率优化的过程吧。(部分引自Hzwer的题解)
首先我们根据题意可以列出动规方程 $$ f[i]=min\{ f[j]+cal(j,i) \}$$
处理$cal(j,i)$可以利用前缀和的思想,令$ sum[i]=\sum_{k=1}^{i} p[k] $
对于物品$1 ~ i$,如果都从$0$运到$i$,则费用为$(sum[i]-sum[j])*x[i]$
但由于物品的起始点不都在0,所以对于每个物品$k$可以少花费$x[k]*p[k]$
定义$b[i]=\sum_{k=1}^{i} (x[k]*p[k]) $
可得 $ f[i]=min\{ f[j]+(sum[i]-sum[j])*x[i]-(b[i]-b[j])+c[i] \} $
下面证明决策单调性:
如果$ j > k $ 且$j$比$k$更优,则有:
\[ \begin{aligned} f[j]+(sum[i]-sum[j])*x[i]-(b[i]-b[j])+c[i] &< f[k]+(sum[i]-sum[j])*x[i]-(b[i]-b[k])+c[i] \\ f[j]-f[k]+b[j]-b[k] &< (sum[j]-sum[k])*x[i] \\ \frac{f[j]-f[k]+b[j]-b[k]}{sum[j]-sum[k]} &< x[i] \end{aligned} \]
至于为什么要证这个东西请看论文:《动态规划的斜率优化》
嗯我们现在就知道了对于每个状态$i$,从1 ~ i-1这些决策中的“当前最优决策”是有一个单调性的!比如我们从1开始枚举到 k ,发现 k 是一个最优决策可以更新答案f[i],然后我们继续枚举直到决策 j ,满足上面那个不等式!则表明决策 j 比决策 k 更优!那么有什么用呢?我们根据不等式发现,这个“更优”的属性,只跟 j 和 k 有关,与阶段 i 是无关的!也就是说,当 j 成为一个可选的方案的时候,k 就永远也不用再考虑它了,这就大大减少了转移时的决策数!从而降低了复杂度!
说的好像很厉害……那具体操作的时候怎么操作呢?
我们用一个队列q来维护一个斜率单调的决策序列:
如果slop(q[l],q[l+1])<x[i](参考上面推出的不等式)则说明q[l+1]这个决策比q[l]这个决策更优,所以l++舍弃队首
算出dp[i]
如果slop(q[r-1],q[r])>slop(q[r],i),则队尾处不满足单调,所以要弹队尾直到满足单调性(联系凸壳的图形想想)
插入决策 i 到队列尾部
当然如果换了一道求max的题,则不等号方向是要改变的……
1 /************************************************************** 2 Problem: 1096 3 User: Tunix 4 Language: C++ 5 Result: Accepted 6 Time:1784 ms 7 Memory:52052 kb 8 ****************************************************************/ 9 10 //BZOJ 1096 11 #include<cstdio> 12 #include<iostream> 13 #include<algorithm> 14 #define rep(i,n) for(int i=0;i<n;++i) 15 #define F(i,j,n) for(int i=j;i<=n;++i) 16 #define D(i,j,n) for(int i=j;i>=n;--i) 17 #define pb push_back 18 using namespace std; 19 int getint(){ 20 int v=0,sign=1; char ch=getchar(); 21 while(ch<'0'||ch>'9'){ if (ch=='-') sign=-1; ch=getchar();} 22 while(ch>='0'&&ch<='9'){ v=v*10+ch-'0'; ch=getchar();} 23 return v*sign; 24 } 25 const int N=1000010; 26 typedef long long LL; 27 /******************tamplate*********************/ 28 int n,l,r,q[N]; 29 LL p[N],x[N],c[N],f[N],b[N],sp[N]; 30 inline double slop(int k,int j){ 31 return double(f[j]-f[k]+b[j]-b[k])/double(sp[j]-sp[k]); 32 } 33 int main(){ 34 int n=getint(); 35 F(i,1,n){ 36 x[i]=getint(); p[i]=getint(); c[i]=getint(); 37 sp[i]=sp[i-1]+p[i]; b[i]=b[i-1]+p[i]*x[i]; 38 } 39 F(i,1,n){ 40 while(l<r && slop(q[l],q[l+1])<x[i]) l++; 41 int t=q[l]; 42 f[i]=f[t]-b[i]+b[t]+(sp[i]-sp[t])*x[i]+c[i]; 43 while(l<r && slop(q[r-1],q[r])>slop(q[r],i))r--; 44 q[++r]=i; 45 } 46 printf("%lld\n",f[n]); 47 return 0; 48 }