C++类型题整理
作者:互联网
【营业记录】时光流韵
也许是一枚能够跨越时空的护符,是先闻其声,或是余音绕梁。
关键词:DP
(1) 内容概要
- 费用提前计算
- 单调栈
- 单调队列
- 斜率优化
(2) 费用提前计算
让我们以 P2365 任务安排 为例。
下文中题目里的费用系数我使用 c_ici 表示。且令 sunTsunT 为 tt 的前缀和,sumCsumC 为 cc 的前缀和。
我们令 f[i,j]f[i,j] 表示把前 ii 个任务分为 jj 批的最小费用。得出:
f[i,j]=min_{0\le k<i}\{f[k,j-1]+(s*j+sumT[i])*(sumC[i]-sumC[k])\}f[i,j]=min0≤k<i{f[k,j−1]+(s∗j+sumT[i])∗(sumC[i]−sumC[k])}
这样是 O(n^3)O(n3)。
然后考虑一个思想费用提前计算。我们发现每次转移时 jj 的存在意义是算出 s*js∗j 然后算出时间。然后我们把这个费用提前,在每次转移的时候为以后的费用考虑好,机器为这次启动花费的 ss 会累加到后面。所以现在可以舍去一维,令 f[i]f[i] 表示把前 ii 个任务分成若干批最小费用,得:
f[i]=min_{0\le j<i}\{f[j]+(sumC[i]-sumC[j])*sumT[i]+s*(sumC[N]-sumC[j])\}f[i]=min0≤j<i{f[j]+(sumC[i]−sumC[j])∗sumT[i]+s∗(sumC[N]−sumC[j])}
这是 O(n^2)O(n2)。然后这题暂时是做完了。简单的小练习:
(3) 单调栈
单调栈,就是要维护一个栈,单调递增或递减。一般地,放入元素时,如果栈顶的元素不符合单调递增(或递减),那就把它弹出,再查栈顶。直到符合或栈空了,再把元素放入。
线性求每个数两侧比它大(或小)的数的位置。
典型的应用是 柱状图中最大的矩形,用单调栈维护每个数两侧比它小的数的位置。
P2422 良好的感觉 同理,只需要一个前缀和优化,就是单调栈的模板题了。
(4) 单调队列
单调队列,就是要维护一个队列,单调递增或递减。一般地,放入元素时,先弹出末尾无效元素。如果队列首的元素不符合单调递增(或递减),那就把它弹出,再查队列首。直到符合或队列空了,再把元素放入。
队列中的元素其对应在原来的列表中的顺序必须是单调递增的。队列中元素的大小必须是单调递增减/甚至是自定义也可以)。
f(x)=opt_{i=bound[x]}^{x-1}(const[i])f(x)=opti=bound[x]x−1(const[i])
对于这样一类动态规划问题,我们可以运用单调队列来解决: 其中 bound[x]bound[x] 随着 xx 单调不降,而 const[i]const[i] 则是可以根据 ii 在常数时间内确定的唯一的常数。
-
P2629 好消息,坏消息 考虑区间 DP 里的终点思想破环成链,套上单调队列。
-
P6040 课后期末考试滑溜滑溜补习班 把 DP 式子推出来以后容易发现可以单调队列优化,这一类的套路题目比较多。
(5) 斜率优化
斜率优化要解决的问题是求 dp[i]=a[i]*b[j]+c[i]+d[j]dp[i]=a[i]∗b[j]+c[i]+d[j]。在最基础的形式中,a[i],b[j]a[i],b[j] 单调递增。如果 a[i]a[i] 不单调递增也没事,我们之后再讨论。
移项:-a[i]b[j]+dp[i]-c[i]=d[j]−a[i]b[j]+dp[i]−c[i]=d[j]。
把 b[j]b[j] 看作 xx ,把 d[j]d[j] 看作 yy,把 -a[i]−a[i] 看作 kk
kx+dp[i]-c[i]=ykx+dp[i]−c[i]=y此直线过 (x,y)(x,y) 且斜率已知为 kk ,要求截距最小。维护一个凸壳。对于每个 ii,要求的是第一个满足 slope(P_j,P_{j+1})>kslope(Pj,Pj+1)>k 的。
一般斜率优化单调队列维护即可。若 aa 无单调性,用二分查找;若 bb 无单调性,用平衡树。
回到 P2365 任务安排,发现这个式子符合斜率优化。
f[i]=f[j]+(sumC[i]-sumC[j])*sumT[i]+s*(sumC[N]-sumC[j])f[i]=f[j]+(sumC[i]−sumC[j])∗sumT[i]+s∗(sumC[N]−sumC[j])
f[i]=f[j]+sumC[i]*sumT[i]-sumC[j]*sumT[i]+sumC[N]*s-sumC[j]*sf[i]=f[j]+sumC[i]∗sumT[i]−sumC[j]∗sumT[i]+sumC[N]∗s−sumC[j]∗s
f[i]=-sumC[j]*sumT[i]+(f[j]-sumC[j]*s)+(sumC[i]*sumT[i]+sumC[N]*s)f[i]=−sumC[j]∗sumT[i]+(f[j]−sumC[j]∗s)+(sumC[i]∗sumT[i]+sumC[N]∗s)
(f[i]-sumC[i]*sumT[i]-sumC[N]*s)+sumC[j]*sumT[i]=f[j]-sumC[j]*s(f[i]−sumC[i]∗sumT[i]−sumC[N]∗s)+sumC[j]∗sumT[i]=f[j]−sumC[j]∗s
b+kx=yb+kx=y
操作步骤如下:
-
对于队首 while(slope(P_{head},P_{head+1})<k) head++while(slope(Phead,Phead+1)<k)head++;
-
队首为最优,计算 dp[i]dp[i];
-
队尾 while(slope(P_{tail-1},P_{tail})>slope(P_{tail-1},P_{i})) tail--while(slope(Ptail−1,Ptail)>slope(Ptail−1,Pi))tail−−;
-
在队尾加入 P_iPi。
考虑一些问题:
你需要注意:
-
使用单调队列的条件是斜率满足单调性质而不是点满足单调性质,如果不满足单调性质可以换用单调栈内二分,复杂度多一个 \loglog。
-
由于两个点的 xx 坐标有可能相同所以需要特判成 \text{inf}inf,如果不特判也许会变成 \text{-inf}-inf。
标签:斜率,队列,sumT,C++,类型,dp,整理,sumC,单调 来源: https://www.cnblogs.com/wangruidong03/p/15817027.html