Наименьшее n-битное целое число c, которое имеет k 1-бит и является суммой двух n-битных целых чисел, для которых g, h битов установлены в 1 (динамическое программирование)

Я пытаюсь решить следующую проблему:

Найдите наименьшее n-битное целое число c, которое имеет k 1-бит и является суммой двух n-битных целых чисел, для которых g, h битов установлены в 1. 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;
}
}

7

Решение

Вы можете построить наименьшую сумму на основе количества битов 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

Пример: все значения k для n = 10, g = 6 и h = 4:

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)))

ч + г = к

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, чтобы проверить правильность, и не обнаружил различий.)

4

Другие решения

Я не слишком убежден в подходе динамического программирования. Если я правильно понимаю, вам нужно определить, как перейти к dp[g + 1][h][k][n], dp[g][h + 1][k][n], dp[g][h][k + 1][n] а также dp[g][h][k][n + 1]с битом переноса и без него, в зависимости от предыдущих вычислений, и я не уверен, каковы правильные правила для всех из них.

Я думаю, что более простой способ думать о проблеме как Поиск дерево, где каждый узел содержит два частичных числа кандидатов для добавления, назовем их G и H. Вы начинаете с узла с G = 0 и H = 0 на уровне m = 0 и работаете следующим образом:

  1. Если G + H имеет n или меньше бит и k 1 бит, то это решение, вы нашли его!
  2. В противном случае, если
    н — м < количество 1 бит в G + H — k
    сбросить узел (решение невозможно).
  3. В противном случае, если
    (g + h) — (количество 1 бит в G + количество 1 бит в H) < k — число 1 бит в G + H
    отбросить узел (нежизнеспособные кандидаты).
  4. В противном случае перейдите на новый уровень. Обычно вы создаете до четырех дочерних элементов каждого узла, добавляя к G и H префиксы с 0 и 0, 0 и 1, 1 и 0 или 1 и 1 соответственно. Тем не мение:
    • Вы можете только предшествовать G с 1, если число 1 битов в G меньше, чем g, и аналогично для H и h.
    • На уровне m (G и H имеют m битов), вы можете только предшествовать G с 0, если
      n — m> g — число 1 бит в G
      и аналогично для H и H.
    • Если G == H и g == h, вы можете пропустить один из 0 и 1 и 1 и 0, так как они приведут к одному и тому же поддереву.
  5. Перейдите к следующему узлу и повторяйте, пока не найдете решение или у вас больше нет узлов для посещения.

Порядок, в котором вы посещаете узлы, важен. Вы должны хранить узлы в приоритетной очереди / куче так, чтобы следующий узел всегда был первым узлом, который потенциально может привести к лучшему решению. Это на самом деле просто, вам просто нужно взять для каждого узла G + H и поставить перед ним префикс с необходимым количеством 1 бит для достижения k; это лучшее возможное решение оттуда.

Возможно, существуют лучшие правила для отказа от недопустимых узлов (шаги 2 и 3), но идея алгоритма та же.

2

По вопросам рекламы [email protected]