我正在尝试解决以下问题:
找到具有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;
}
}
答案 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,并且工作如下:
您访问节点的顺序非常重要。您应该将节点存储在优先级队列/堆中,以便下一个节点始终是可能导致最佳解决方案的第一个节点。这实际上很简单,你只需要为每个节点G + H取前缀并用必要数量的1位前缀来达到k;那是那里最好的解决方案。
丢弃无效节点可能有更好的规则(步骤2和3),但算法的想法是相同的。