【题解】P9499 dp凸优化 复杂度分析
很典但也很妙的题。
首先有一个显然暴力的 dp:从前往后一个一个换,记录目前剩的货币的数量,$f_{i,j}$ 目前换完第换完第 $i$ 种货币,保留了 $j$ 张,可以赚到多少钱,转移如下:
兑换货币:
$$ f_{i,\lfloor\frac{j}{x_{i-1}}\rfloor+c_i}={f_{i-1,j}} $$
在这一步将 $k$ 张货币用于获取价值,剩下的保留换取更靠后的货币(完全背包):
$$ f_{i,j}=\max_{0\leq k\leq j}\{f_{i,j+k}+kv\} $$
和值域相关,需要优化,往往要把这种直接放在值域上的东西转化成分段离散的东西来维护,我们把 dp 拍平(滚动数组)观察转移是对 dp 数组进行怎样的操作:
- 在开头放 $c$ 个 $0$,整体右移。
- 从后往前,每个位置加上 $v$ 去更新前一个位置(完全背包)。
- 将数组“放缩 $x$ 倍”,即只保留 $x,2x,\dots,kx$ 这些项。
这种形态的 dp 往往都不太好维护或是直接优化转移,分析可以发现 dp 值构成了一个上凸包:
- 显然 dp 值是单调递减的,所以在开头插入一段 $0$ 不影响凸性。
- 从后往前,每个位置加上 $v$ 去更新前一个位置,等价于与数组 $\{0,v,2v,....,kv\}$ 做一个闵可夫斯基和(只不过是差卷积的形态)。
- 放缩 $x$ 倍后显然不改变凸性:其单减的差分数组上两段等长的区间,靠前那段一定和不会更小。
考虑维护这个上凸包,经典地,维护差分数组的分段函数。
前两种操作是很经典的,第一种操作直接在数组的前面插入一段 $0$,第二种操作相当于将前面一段斜率小于 $v$ 的值给弹出,换成斜率为 $v$ 的段。
第三种操作我们直接暴力完成(当 $x>1$ 时):
对于每个原来凸包上的拐点,找到其前后第一个横坐标为 $x$ 的倍数的点称作特殊点,然后将相邻两个特殊点连起来。
两个中间有拐点的特殊点在缩放后的横坐标差为 $1$,直接计算斜率,而中间没有拐点的特殊点直接将斜率扩大 $x$ 倍即可。
下面我们来证明直接暴力完成的复杂度是正确的:
-
首先每次暴力操作会让所有点的坐标至少减半,然后两个凸包拐点的距离也会至少变成除二上取整。
-
虽然每次放缩有可能会使凸包的点数翻倍(每个点前后各取一个),但是这些距离为 $1$ 的点对在下一次放缩时一定会每组消失至少一个。
所以复杂度为 $O(n\log \sum c_i)$,常数不大。
本文已经结束了。本文作者:ღꦿ࿐(DeepSea),转载请注明原文链接:https://www.cnblogs.com/Dreamerkk/p/17970909,谢谢你的阅读或转载!