【题解】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)$,常数不大。

posted @ 2023-08-16 17:40  寂静的海底  阅读(12)  评论(0编辑  收藏  举报  来源