[关闭]
@ljt12138 2017-03-26T22:11:47.000000Z 字数 6144 阅读 1517

斜率优化dp学习笔记

一年前就像看斜率优化dp了...然而一直没有看懂。今天花了一天时间总算了解了个大概。这篇文章将大致分析斜率优化dp的原理和应用。

[HNOI2008]玩具装箱TOY

Description

P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。P教授有编号为1...NN件玩具,第i件玩具经过压缩后变成一维长度为Ci.为了方便整理,P教授要求在一个一维容器中的玩具编号是连续的。同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物,形式地说如果将第i件玩具到第j个玩具放到一个容器中,那么容器的长度将为 x=ji+Ck,iKj 制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为X,其制作费用为(XL)2.其中L是一个常量。P教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过L。但他希望费用最小.

Input

Output

Sample

Input

  1. 5 4
  2. 3
  3. 4
  4. 2
  5. 1
  6. 4

Output

  1. 1

Solution

首先一眼dp方程:

f(i)=min0j<n{f(j)+(ij1+s[i]s[j]L)2}

其中:

s[i]=1kiCi

然而这是个1D/1D方程,如果直接处理复杂度是O(n2)的。状态已经没有办法再简化了,我们只能考虑在转移时做文章。

我们记k(i)为使得f(i)取最小值的j,把方程中对每个i转移的k(i)打一个表,可以发现k(i)是单调不减的。我们考虑证明之。

1. 证明决策单调性

首先对原方程做一些化简。令T(i)=i+s[i],c=L+1,并用f(i,k)表示f(i)jk时的值,即:

f(i,j)=f(j)+(T(i)T(j)c)2

分析可知要证明:

i<j,k(i)k(j)

根据k(i)的定义显然有:

j,f(i,k(i))f(j)

任取i,t满足i<t,只需证:

j(jk(i)),f(t,k(i))f(t,j)

只需证:

f(k(i))+(T(t)T(k(i))c)2f(j)+(T(t)T(j)c)2

T(t)T(i)=v,有:

f(k(i))+(T(i)+vT(k(i))c)2f(j)+(T(i)+vT(j)c)2

展开得到:

f(k(i))+(T(t)T(k(i))c)2+v2+2v(T(t)T(k(i))c)f(j)+(T(t)T(j)c)2+v2+2v(T(t)T(j)c)

由于f(k(i))+(T(t)T(k(i))c)2+v2f(j)+(T(t)T(j)c)2+v2等价于f(i,k(i))f(j),只需证:

2v(T(t)T(k(i))c)2v(T(t)T(j)c)

只需证:

T(k(i))T(j)

由于jk(i)T(x)单调增,上式显然成立。

2. 利用单调性解决问题

考虑何时有f(i)kj且取k比取j更优。根据定义:

f(i,k)f(i,j)

就是

f(k)+(T(i)T(k)c)2f(j)+(T(i)T(j)c)2

展开并分离变量,得到:

T(i)f(k)f(j)2(T(k)T(j))+T(k)+T(j)2+c    (!)

这时左边只剩下了和i有关的常量,右边是和k,j有关的代数式。

我们发现这个式子不仅关于k,j对称,而且可以写成y(j)y(k)x(j)x(k)+c的形式,前面可以看成斜率的表示。用w(i,j)表示这个斜率,如果a<b<cw(a,b)>w(b,c)T(i)w(b,c)的条件比T(i)w(a,b)更“松”,因而a是不可能被选的。这样“可供选择”的点两两间的斜率形如一个下凸壳,就可以用单调队列维护指标递增-斜率递增的双递增序列。

至此我们成功地获得了一个算法:每次加入新节点时,从队列左端扫去不符合要求的点,剩下的第一个点就是所需的最小答案。之后将当前点插入队列右端并维护下凸性。由于每个元素入队一次出队一次,复杂度为:O(n)

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int MAXN = 50005;
  4. long long s[MAXN], f[MAXN], L;
  5. int n;
  6. long long q[MAXN];
  7. int l = 1, r = 0;
  8. inline long long T(int i)
  9. { return i+s[i]; }
  10. long long c;
  11. inline double slop(int j, int k)
  12. { return 0.5*(f[k]-f[j])/(T(k)-T(j))+0.5*(T(k)+T(j))+c; }
  13. int main()
  14. {
  15. scanf("%d%lld", &n, &L);
  16. c = L+1;
  17. for (int i = 1; i <= n; i++)
  18. scanf("%lld", &s[i]), s[i] += s[i-1];
  19. memset(f, 127/3, sizeof f);
  20. f[0] = 0;
  21. q[++r] = 0;
  22. for (int i = 1; i <= n; i++) {
  23. while (l < r && slop(q[l], q[l+1]) <= T(i)) l++;
  24. f[i] = f[q[l]]+(T(i)-T(q[l])-c)*(T(i)-T(q[l])-c);
  25. while (l < r && slop(q[r-1], q[r]) >= slop(q[r-1], i)) r--;
  26. q[++r] = i;
  27. }
  28. cout << f[n] << endl;
  29. return 0;
  30. }

四边形不等式

一维情景

在证明决策单调性的过程中计算量非常大,有时可以用四边形不等式简化运算:记j转移到i的代价f(i)f(j)=w(ji)。w被称为“凸”的,如果

a<b<c<d,w(ac)+w(bd)w(ad)+w(bc)

下面的命题等价:

  1. w为凸。
  2. w(ab)+w(a+1b+1)w(ab+1)+(a+1b)
  3. f决策单调

二维情景

d(i,j)=mini<k<j{d[i,k1]+d[k+1,j]+w[i,j]}

则下列命题等价:

  1. w为凸。
  2. w(ab)+w(a+1b+1)w(ab+1)+(a+1b)
  3. k(i,j1)k(i,j)k(i+1,j)

还记得dp入门题“石子归并”的方程:

d(i,j)=minik<j{d[i,k]+d[k+1,j]+sum[i,j]}

由于sum满足凸性,可以用记忆化搜索记忆k(i,j)将复杂度降到O(n2)

CEOI2004 锯木厂选址

Description

从山顶上到山底下沿着一条直线种植了n棵老树。当地的政府决定把他们砍下来。为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂。
木材只能按照一个方向运输:朝山下运。山脚下有一个锯木厂。另外两个锯木厂将新修建在山路上。你必须决定在哪里修建两个锯木厂,使得传输的费用总和最小。假定运输每公斤木材每米需要一分钱。

Input

Output

Sample

Input

  1. 9
  2. 1 2
  3. 2 1
  4. 3 3
  5. 1 1
  6. 3 2
  7. 1 6
  8. 2 1
  9. 1 2
  10. 1 1

Output

  1. 26

Solution

“两个”不仅让我们浮想联翩。可以确定一个,枚举另一个作为决策。设dsi为i距离第一棵树的距离,si为前i棵树的总重,定义f(i)

f(i)=1kjwk(dsjdsk)+j<kiwk(didk)+i<knwk(dn+1dk)

化简得到:

f(i)=min{Ti+djsjdisj}

其中:Ti=disi+dn+1(snsi)dkwk。用四边形不等式证明这个函数满足决策单调性:

djsjdisj+dj+1sj+1di+1sj+1djsjdi+1sj+dj+1sj+1disj+1

化简得到:

didi+1

这是显然的。因此可以考虑斜率优化。仿照上面的推导过程可以得到:

didkskdjsjsksj

大功告成!原式被整理成了斜率的形式,只需要模仿上面不出脑残错误就可以1A了!(事实上我斜率写错调了1.5h...)

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. int n;
  4. long long s[20005], d[20005], a[20005], b[20005];
  5. long long q[20005];
  6. int l = 0, r = 0;
  7. double slop(int j, int k)
  8. { return 1.0*(d[k]*s[k]-d[j]*s[j])/(s[k]-s[j]); }
  9. int main()
  10. {
  11. freopen("two.in", "r", stdin);
  12. freopen("two.out", "w", stdout);
  13. scanf("%d", &n);
  14. for (int i = 1; i <= n; i++)
  15. scanf("%lld%lld", &a[i], &b[i]);
  16. d[1] = 0;
  17. for (int i = 2; i <= n+1; i++) d[i] = d[i-1]+b[i-1];
  18. s[0] = 0;
  19. for (int i = 1; i <= n; i++) s[i] = s[i-1]+a[i];
  20. ///////
  21. long long T = 0;
  22. for (int i = 1; i <= n; i++) T -= a[i]*d[i];
  23. long long ans = 233333333333ll;
  24. for (int i = 1; i <= n; i++) {
  25. while (l < r && slop(q[l], q[l+1]) <= d[i]) l++;
  26. ans = min(ans, T+d[i]*s[i]-d[n+1]*s[i]+d[n+1]*s[n]-d[i]*s[q[l]]+d[q[l]]*s[q[l]]);
  27. while (l < r && slop(q[r-1], q[r]) >= slop(q[r-1], i)) r--;
  28. q[++r] = i;
  29. }
  30. cout << ans << endl;
  31. return 0;
  32. }

参考资料

  1. http://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html
  2. http://blog.csdn.net/pi9nc/article/details/9533107
  3. http://www.cnblogs.com/kedebug/archive/2013/03/02/2940423.html
  4. http://hzwer.com/2114.html
  5. https://wenku.baidu.com/view/eeb6d3ea19e8b8f67c1cb937.html
  6. 《算法艺术与信息学竞赛(刘汝佳,黄亮)》
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注