最小的n位整数c,具有k 1位,是两个n位整数的总和,其中g,h位设置为1(动态编程)

时间:2017-09-25 19:32:21

标签: c++ algorithm dynamic-programming top-down

我正在尝试解决以下问题:

  

找到具有k 1位的最小n位整数c,并且是g,h位设置为1的两个n位整数之和.g,h,k< = n

首先,这里的n位整数意味着我们可以使用所有n位,即最大值。这样的整数的值是2^n - 1。所描述的整数可能根本不存在。 显而易见,k > g + h没有解决方案,g + h = k答案仅为2^k - 1(第一k位为1位,k - n为零前)。

至于程序应该做什么的一些例子:

g = h = k = 4, n = 10 :
0000001111 + 0000001111 = 0000011110
15 + 15 = 30 (30 should be the output)


(4, 6, 5, 10):
0000011110 + 0000111111 = 0001011101
30 + 63 = 93

(30, 1, 1, 31):
1 + (2^30 - 1) = 2^30

正如我所想,这是一个动态编程问题,我选择了以下方法: 设dp[g][h][k][n][c]为描述的整数,c是携带的可选位。我尝试根据最低位来重建可能的总和。 因此,dp[g][h][k][n + 1][0]

的最小值
(0, 0):       dp[g][h][k][n][0]
(0, 0): 2^n + dp[g][h][k - 1][n][1]
(1, 0): 2^n + dp[g - 1][h][k - 1][n][0]
(0, 1): 2^n + dp[g][h - 1][k - 1][n][0]

同样,dp[g][h][k][n + 1][1]

的最小值
(1, 1): dp[g - 1][h - 1][k][n][0]
(1, 1): dp[g - 1][h - 1][k - 1][n][1] + 2^n
(1, 0): dp[g - 1][h][k][n][1]
(0, 1): dp[g][h - 1][k][n][1]

这个想法并不是那么难,但我对这些事情并不熟悉,即使对于最简单的情况,我的算法也不起作用。我选择了自上而下的方法。我很难考虑所有的角落案例。我真的不知道我是否正确选择了递归的基础等。我的算法甚至不适用于g = h = k = 1, n = 2的最基本情况(答案是01 + 01 = 10)。 g = h = k = 1, n = 1不应该有答案,但算法会给出1(这基本上是前一个示例输出1而不是2的原因)。 所以,这里是我糟糕的代码(只有非常基本的C ++):

int solve(int g, int h, int k, int n, int c = 0) {
  if (n <= 0) {
    return 0;
  }
  if (dp[g][h][k][n][c]) {
    return dp[g][h][k][n][c];
  }
  if (!c) {
    if (g + h == k) {
      return dp[g][h][k][n][c] = (1 << k) - 1;
    }
    int min, a1, a2, a3, a4;
    min = a1 = a2 = a3 = a4 = std::numeric_limits<int>::max();
    if (g + h > k && k <= n - 1) {
      a1 = solve(g, h, k, n - 1, 0);
    }
    if (g + h >= k - 1 && k - 1 <= n - 1) {
      a2 = (1 << (n - 1)) + solve(g, h, k - 1, n - 1, 1);
    }
    if (g - 1 + h >= k - 1 && k - 1 <= n - 1) {
      a3 = (1 << (n - 1)) + solve(g - 1, h, k - 1, n - 1, 0);
    }
    if (g + h - 1 >= k - 1 && k - 1 <= n - 1) {
      a4 = (1 << (n - 1)) + solve(g, h - 1, k - 1, n - 1, 0);
    }
    min = std::min({a1, a2, a3, a4});
    return dp[g][h][k][n][c] = min;
  } else {
    int min, a1, a2, a3, a4;
    min = a1 = a2 = a3 = a4 = std::numeric_limits<int>::max();
    if (g - 2 + h >= k && k <= n - 1) {
      a1 = solve(g - 1, h - 1, k, n - 1, 0);
    }
    if (g - 2 + h >= k - 1 && k - 1 <= n - 1) {
      a2 = (1 << (n - 1)) + solve(g - 1, h - 1, k - 1, n - 1, 1);
    }
    if (g - 1 + h >= k && k <= n - 1) {
      a3 = solve(g - 1, h, k, n - 1, 1);
    }
    if (g - 1 + h >= k && k <= n - 1) {
      a4 = solve(g, h - 1, k, n - 1, 1);
    }
    min = std::min({a1, a2, a3, a4});
    return dp[g][h][k][n][c] = min;
  }
}

2 个答案:

答案 0 :(得分:4)

您可以根据位计数g,h和k构建最小的和,而无需进行任何动态编程。假设g≥h(否则切换它们)这些是规则:

k≤h≤g

      11111111  <-  g ones  
  111100000111  <-  h-k ones + g-k zeros + k ones
 1000000000110  <-  n must be at least h+g-k+1

h≤k≤g

    1111111111  <-  g ones  
      11111100  <-  h ones + k-h zeros
    1011111011  <-  n must be at least g+1

h≤g≤k

 1111111100000  <-  g ones + k-g ones  
 1100000011111  <-  g+h-k ones, k-h zeros, k-g ones
11011111111111  <-  n must be at least k+1, or k if g+h=k

示例:对于n = 10,g = 6和h = 4:

,k的所有值
k=1           k=2           k=3           k=4       
0000111111    0000111111    0000111111    0000111111
0111000001    0011000011    0001000111    0000001111
----------    ----------    ----------    ----------
1000000000    0100000010    0010000110    0001001110
  
k=4           k=5           k=6
0000111111    0000111111    0000111111
0000001111    0000011110    0000111100
----------    ----------    ----------
0001001110    0001011101    0001111011
  
k=6           k=7           k=8           k=9           k=10
0000111111    0001111110    0011111100    0111111000    1111110000
0000111100    0001110001    0011000011    0100000111    0000001111
----------    ----------    ----------    ----------    ----------
0001111011    0011101111    0110111111    1011111111    1111111111

或者,直接转到c的值而不先计算a和b:

k≤h≤g

c = (1 << (g + h - k)) + ((1 << k) - 2)

h≤k≤g

c = (1 << g) + ((1 << k) - 1) - (1 << (k - h))

h≤g≤k

c = ((1 << (k + 1)) - 1) - (1 << ((g - h) + 2 * (k - g)))

h + g = k

c = (1 << k) - 1

导致这个令人失望的平凡代码:

int smallest_sum(unsigned n, unsigned g, unsigned h, unsigned k) {
    if (g < h) {unsigned swap = g; g = h; h = swap;}
    if (k == 0) return (g > 0 || h > 0 || n < 1) ? -1 : 0;
    if (h == 0) return (g != k || n < k) ? -1 : (1 << k) - 1;
    if (k <= h) return (n <= h + g - k) ? -1 : (1 << (g + h - k)) + ((1 << k) - 2);
    if (k <= g) return (n <= g) ? -1 : (1 << g) + ((1 << k) - 1) - (1 << (k - h));
    if (k < g + h) return (n <= k) ? -1 : (1 << (k + 1)) - 1 - (1 << (2 * k - g - h));
    if (k == g + h) return (n < k) ? -1 : (1 << k) - 1;
    return -1;
}

一些示例结果:

n=31, g=15, h=25, k=10  ->  1,073,742,846  (1000000000000000000001111111110)
n=31, g=15, h=25, k=20  ->     34,602,975  (0000010000011111111111111011111)
n=31, g=15, h=25, k=30  ->  2,146,435,071  (1111111111011111111111111111111)

(我将结果与蛮力算法的结果进行比较,对于n,g,h和k的每个值,从0到20检查正确性,发现没有差异。)

答案 1 :(得分:2)

我不太相信动态编程方法。如果我理解正确,您需要定义如何使用和不使用进位位转到dp[g + 1][h][k][n]dp[g][h + 1][k][n]dp[g][h][k + 1][n]dp[g][h][k][n + 1],以前的计算功能,我不确定所有这些规则的正确规则。

我认为考虑问题的一种更简单的方法是作为A* search树,其中每个节点包含两个要添加的部分候选数字,让我们称之为G和H.你从一个G = 0的节点开始并且在m = 0的水平H = 0,并且工作如下:

  1. 如果G + H有n位或更少位且k 1位,那就是解决方案,你就找到了它!
  2. 否则,如果是 n - m < G + H中的1位数 - k
    丢弃节点(无法解决)。
  3. 否则,如果是 (g + h) - (G中的1位数+ H中的1位数)&lt; k - G + H中的1位数 丢弃节点(不可行的候选人)。
  4. 否则,将节点分支到新级别。通常,每个节点最多包含4个子节点,前缀为G和H,分别为0和0,0和1,1和0或1和1。然而:
    • 如果G中的1位数小于g,则只能在G之前加1;对于H和h,同样如此。
    • 在等级m(G和H有m位)时,如果是,则只能在G之前加0 n - m> g - G中的1位数 和H和h类似。
    • 如果G == H且g == h,则可以跳过0和1以及1和0中的一个,因为它们将导致相同的子树。
  5. 继续到下一个节点并重复,直到找到解决方案或者您没有更多节点可供访问。
  6. 您访问节点的顺序非常重要。您应该将节点存储在优先级队列/堆中,以便下一个节点始终是可能导致最佳解决方案的第一个节点。这实际上很简单,你只需要为每个节点G + H取前缀并用必要数量的1位前缀来达到k;那是那里最好的解决方案。

    丢弃无效节点可能有更好的规则(步骤2和3),但算法的想法是相同的。