我有一个大小为n的整数值和给定数字S的数组.
1<=n<=30
我想找到子序列的总数,使得对于每个子序列,元素sum小于S.
例如:设n = 3,S = 5,数组的元素为{1,2,3},则其总子序列为7 –
{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3}
但是,所需的子序列是:
{1},{2},{3},{1,2},{1,3},{2,3}
因为其元素和是(1 2 3)= 6,其大于S,即6> S,所以不采用{1,2,3}.取其他因为,对于其他子序列,元素和小于S.
因此,可能的子序列总数为6.
所以我的答案是数,这是6.
我尝试过递归方法但其时间复杂度为2 ^ n.
请帮助我们在多项式时间内完成.
最佳答案 你可以在合理的时间(可能)使用伪多项式算法解决背包问题,如果数字被限制为正(或者,技术上,零,但我将假设为正).它被称为伪多项式,因为它在nS时间内运行.这看起来是多项式的.但事实并非如此,因为问题有两个复杂性参数:第一个是n,第二个是S的“大小”,即S中的位数,称之为M.所以这个算法实际上是n 2 ^ M .
为了解决这个问题,让我们定义一个二维矩阵A.它有n行和S列.我们将说A [i] [j]是可以使用前i个元素形成的子序列的数量,并且最大总和最多为j.立即观察到A的右下角元素是解,即A [n] [S](是的,我们使用的是基于1的索引).
现在,我们想要一个A [i] [j]的公式.观察到使用第一个i元素的所有子序列要么包含第i个元素,要么不包含.不具有的子序列的数量仅为A [i-1] [j].做的子序列的数量只是A [i-1] [j-v [i]],其中v [i]只是第i个元素的值.那是因为通过包含第i个元素,我们需要将总和的余数保持在j-v [i]之下.因此,通过添加这两个数字,我们可以组合包含第j个元素和不包含第j个元素的子序列来获取总数.所以这导致我们使用以下算法(注意:我对元素和i使用基于零的索引,但是基于j使用1):
std::vector<int> elements{1,2,3};
int S = 5;
auto N = elements.size();
std::vector<std::vector<int>> A;
A.resize(N);
for (auto& v : A) {
v.resize(S+1); // 1 based indexing for j/S, otherwise too annoying
}
// Number of subsequences using only first element is either 0 or 1
for (int j = 1; j != S+1; ++j) {
A[0][j] = (elements[0] <= j);
}
for (int i = 1; i != N; ++i) {
for (int j = 1; j != S+1; ++j) {
A[i][j] = A[i-1][j]; // sequences that don't use ith element
auto leftover = j - elements[i];
if (leftover >= 0) ++A[i][j]; // sequence with only ith element, if i fits
if (leftover >= 1) { // sequences with i and other elements
A[i][j] += A[i-1][leftover];
}
}
}
运行该程序,然后输出A [N-1] [S],根据需要产生6.如果这个程序运行速度不够快,你可以通过使用单个向量而不是向量向量来显着提高性能(并且你可以通过不浪费一个列来保存一些空间/性能,以便为1指数,就像我做的那样).