优化 DP 解决方案的空间复杂度

Posted

技术标签:

【中文标题】优化 DP 解决方案的空间复杂度【英文标题】:Optimizing Space complexity for a DP solution 【发布时间】:2015-12-11 00:12:00 【问题描述】:

问题

我有一张以 10 像素(一维数组)形式给出的照片(黑白) 字符“b”和“w”,b 表示黑色,w 表示白色)。 I 也有N个过滤器。每个过滤器都是一个一维数组(大小为 10),包括 '+' 和 ' - ' 。您可以选择这 N 个过滤器的任何子集并应用 他们在照片上。 应用过滤器:如果 ith 过滤器的字符是 '+' 反转 ith 像素 照片没有任何反应。我们需要输出的数量 我们可以选择不同的过滤器子集将照片转换成 全黑。

约束

1 15

我的方法

基本思想是我有一个函数 NumberPossible(i, 起始位置),其中 i 是尚未使用的过滤器的索引。 i 从 0 到 n 不等。 基本情况:NumberPossible(n, 当且仅当 startingPosition 为 all 时,startingPosition) 等于 1 黑色。否则为 0。 重复:NumberPossible(i, 起始位置) = NumberPossible(i+1, applyFilter(i, startPosition)) + NumberPossible(i+1, startingPosition) 那 表示您如何应用过滤器或不应用 过滤器。

问题

以下方法超时(可能是由于昂贵步骤 代码中提到)。有没有更好的算法,我在哪里 不需要存储所有状态并且需要更少的空间 复杂性?

【问题讨论】:

每隔一段时间就会以某种形式提出同样的问题。他们弄错了其他事情,但通常认识到您一次只需要两行 dp,当前和以前,因此您可以使用 dp[i&1][y]dp[(i+1)&1][y] 所以时间复杂度是相同的(但由于缓存而更快)并且空间被削减到非常微不足道。您还需要按所需数据的顺序进行计算,而不是递归。 但是,由于只有 1024 个可能的过滤器并且原始序列无关紧要,因此您可以通过计算每个可用的数量并按批次使用它们而不是单独使用它们来做得更好。跨度> @JSF 如果我更改 dp[i][y] to dp[i&1][y]dp[(i+1)][y] to dp[(i+1)&1][y],它会给出错误的输出。 计算的递归序列不允许您使用主要的空间优化。您需要切换到前向计算序列,计算另一个维度上的所有值(而不是按需),因为很难预测您将需要哪个。按需节省的成本似乎太重要了,不能放弃。但是,检测之前是否进行过计算的成本实际上加起来超过了按需提供的总收益。 @JSF 我完全不明白你在说什么:/ 一点伪代码可能会有所帮助。 【参考方案1】:

DP 的概念是,您从最终需要的信息到您需要的所有中间信息递归地向后工作,一直到您可以直接计算的东西。同时,您避免通过递归重新计算多条路径中所需的任何中间值。

但是 DP 的典型现实是,知道哪些中间值已经计算出来的成本使得实际在相反方向工作更便宜。从您可以直接计算的内容开始。计算所有这些东西,不管你知道你是否需要它们。然后转向可以从这些计算的东西并计算所有这些,再次不知道你是否需要它们。在典型情况下,与以有效顺序计算事物的好处相比,“无论您最终是否需要它”的多余计算都是微不足道的。

存在语义问题(我看到这两个方面都积极维护)第二种方法是否真的是 DP,或者 DP 是否只是第一种方法并且假设的 DP 解决方案用于帮助定义非 DP 解决方案。

你的问题中的方向是这个讨论中的一个令人困惑的因素,因为这两种方法都允许可逆方向,但两种方法之间的主要区别是方向相反。真正发生的是与存储答案的含义相对相反的方向。

所以让我们假设我们已经将j 计算为 0 或 1,dp[j][Y] 对于所有 Y 表示考虑到所有输入的组合之前 i 用于某些i 以及ij 之间的某种关系(所有这些都发生在i 的循环内)。

接下来,我们可以循环 Y 以计算所有 Y 代表组合的 dp[1-j][Y],同时考虑之前的所有输入并包括输入 i

之后,我们要么将j1-j 交换(只需编码j=1-j;),要么从i 的下一个值重新计算j

我们可能希望检测并跳过输入中的一些空位置,这成为将j1-j 交换可能会更好地作为独立操作而不是递增i 的原因。但这是一个相当高级的优化。

由于您知道如何从 dp[i][]y 计算 dp[i+1][y] 并输入 i,因此您知道如何将所有这些放入 i 的循环和 y 的内循环中,但使用 @ 987654344@ 和 dp[1-j][] 代替。

接下来,更大的优化是计算输入。由于输入序列无关紧要,我们可以完全跳过filters并使用filter_count[x]++,而不是存储filters[i]=x

然后i 的主循环遍历filter_count 的1024 个元素,而不是filter 的N 个元素。

sometype global_multiplier=1;
for (unsigned i=0; i<1024; ++i)
if (filter_count[i])   // only do the work if we have any
    // Multiply by half the number of subsets of this filter
    for (unsigned c=filter_count[i]; --c;)
        global_multiplier=(global_multiplier*2)%MOD;

    for (unsigned y=0; y<1024; ++y)
        dp[1-j][y] = (dp[j][y] + dp[j][y^i]) % MOD;
    j=1-j;
    

最后选择dp[j][]的正确位置并乘以单独保存的global_multiplier

对于单个最终乘法,您需要的临时数字是 MOD 中数字的两倍。但是在其他任何地方,您只需要比 MOD 中的位多一位的值。如果真的强制,最后的乘法可以通过移位和加法完成,因此它也只需要比 MOD 本身需要的多一位。

【讨论】:

我不认为我们可以做到unsigned c=1&lt;&lt;(filter_count[i]-1); filter_count 在这里可以达到 10^5。 @SarvagyaAgarwal 好点。我认为这也解释了原始代码中的所有 MOD 使用。问题文本中未提及,“答案”大得不合理,并且分配用于答案模式1e9+7,因此必须将MOD操作注入我的代码中。 对不起,我会编辑。我确实提到了对 N 的限制。同样在您的代码中,您没有初始化 j @SarvagyaAgarwal 这整个答案是为了响应您在早期 cmets 中对伪代码的请求。所以它应该在没有簿记的情况下显示困难的想法。我假设将 j 初始化为零或一是显而易见的(只要您正确初始化 dp[j][] 都可以) 我想我现在明白了大部分。关于初始化dp[j][[] 表,dp[0][0]=1;dp[1][0]=1;rest all 0 是正确的,对吧?!我看看这个方法行不行!

以上是关于优化 DP 解决方案的空间复杂度的主要内容,如果未能解决你的问题,请参考以下文章

背包问题

DP悬线法奶牛浴场

hdu 1024

关于时间和空间复杂度的优化心得分享(C# )

程序优化--降低复杂度

程序优化--降低复杂度