Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/143.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 子集总和(硬币兑换)_C++_Algorithm - Fatal编程技术网

C++ 子集总和(硬币兑换)

C++ 子集总和(硬币兑换),c++,algorithm,C++,Algorithm,我的问题是,我需要计算一个值W中有多少整数数组的组合` 比如说: int array[] = {1,2,3,4,5}; 我的算法就是找到从1到W/最小值(数组)的所有长度组合,这等于W,因为最小值是1。 检查每个组合的和是否等于W,然后增加一个计数器N 还有其他算法可以解决这个问题吗?应该更快:) 更新: 子集问题和背包问题都很好,但我的问题是数组的组合会重复元素,如下所示: 1,1,1 -> the 1st combination 1,1,2 1,1,3 1,1,4 1,1,5 1,2

我的问题是,我需要计算一个值
W
中有多少整数数组的组合`

比如说:

int array[] = {1,2,3,4,5};
我的算法就是找到从
1
W/最小值(数组)
的所有长度组合,这等于
W
,因为最小值是1。 检查每个组合的和是否等于W,然后增加一个计数器
N

还有其他算法可以解决这个问题吗?应该更快:)

更新: 子集问题和背包问题都很好,但我的问题是数组的组合会重复元素,如下所示:

1,1,1 -> the 1st combination
1,1,2
1,1,3
1,1,4
1,1,5
1,2,2 -> this combination is 1,2,2, not 1,2,1 because we already have 1,1,2. 
1,2,3
1,2,4
1,2,5
1,3,3 -> this combination is 1,3,3, not 1,3,1 because we already have 1,1,3. 
1,3,4
.
.
1,5,5
2,2,2 -> this combination is 2,2,2, not 2,1,1 because we already have 1,1,2. 
2,2,3
2,2,4
2,2,5
2,3,3 -> this combination is 2,3,3, not 2,3,1 because we already have 1,2,3.
.
.
5,5,5 -> Last combination
这些都是长度为3的
{1,2,3,4,5}
的组合。子集和问题给出了另一种我不感兴趣的组合

因此,求和为
W
的组合,假设
W=7

2,5
1,1,5
1,3,3
2,2,3
1,1,2,3
1,2,2,2
1,1,1,1,3
1,1,1,2,2
1,1,1,1,1,2
1,1,1,1,1,1,1
更新:
真正的问题在于元素的重复
1,1,1
是需要的,并且生成组合的顺序并不重要,因此
1,2,1
1,1,2
2,1,1
目前没有有效的算法存在,可能永远不会(NP完全问题)


这是的(一种变体)。

这是。它可以通过动态规划来解决,合理地限制W和集合大小

下面是解决这个问题的代码。我相信它运行在O(W/min(A))时间内。评论应该足以说明它是如何工作的。重要的细节是,它可以多次使用一个元素,但一旦停止使用该元素,它就再也不会使用它了。这避免了重复计算[1,2,1]和[1,1,2]之类的内容

package main

import (
  "fmt"
  "sort"
)

// This is just to keep track of how many times we hit ninjaHelper
var hits int = 0

// This is our way of indexing into our memo, so that we don't redo any
// calculations.
type memoPos struct {
  pos, sum int
}

func ninjaHelper(a []int, pos, sum, w int, memo map[memoPos]int64) int64 {
  // Count how many times we call this function.
  hits++

  // Check to see if we've already done this computation.
  if r, ok := memo[memoPos{pos, sum}]; ok {
    return r
  }

  // We got it, and we can't get more than one match this way, so return now.
  if sum == w {
    return 1
  }

  // Once we're over w we can't possibly succeed, so just bail out now.
  if sum > w {
    return 0
  }

  var ret int64 = 0
  // By only checking values at this position or later in the array we make
  // sure that we don't repeat ourselves.
  for i := pos; i < len(a); i++ {
    ret += ninjaHelper(a, i, sum+a[i], w, memo)
  }

  // Write down our answer in the memo so we don't have to do it later.
  memo[memoPos{pos, sum}] = ret
  return ret
}

func ninja(a []int, w int) int64 {
  // We reverse sort the array.  This doesn't change the complexity of
  // the algorithm, but by counting the larger numbers first we can hit our
  // target faster in a lot of cases, avoid a bit of work.
  sort.Ints(a)
  for i := 0; i < len(a)/2; i++ {
    a[i], a[len(a)-i-1] = a[len(a)-i-1], a[i]
  }
  return ninjaHelper(a, 0, 0, w, make(map[memoPos]int64))
}

func main() {
  a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
  w := 1000
  fmt.Printf("%v, w=%d: %d\n", a, w, ninja(a, w))
  fmt.Printf("Hits: %v\n", hits)
}
主程序包
进口(
“fmt”
“排序”
)
//这只是为了记录我们打了多少次忍者
var命中int=0
//这是我们在备忘录中建立索引的方式,这样我们就不会重做任何事情
//计算。
类型memoPos结构{
位置和整数
}
func ninjaHelper(a[]int,pos,sum,w int,备忘录映射[memoPos]int64)int64{
//计算调用此函数的次数。
击中++
//检查一下我们是否已经做了这个计算。
如果r,ok:=memo[memoPos{pos,sum}];ok{
返回r
}
//我们知道了,这样我们只能赢一场比赛,现在就回来吧。
如果和=w{
返回1
}
//一旦我们结束w,我们就不可能成功,所以现在就退出吧。
如果总和>w{
返回0
}
var ret int64=0
//只检查数组中此位置或更高位置的值
//确保我们不会重复我们自己。
对于i:=pos;i
为了解决这个问题,这里有一些递归和(非常简单的)动态编程解决方案。您可以通过使用更复杂的终止条件来减少递归解决方案的运行时间(但不是时间复杂度),但其要点是显示逻辑

我见过的许多动态规划解决方案都保留了整个nx | c |结果数组,但这不是必需的,因为行I可以从行I-1生成,而且它可以从左到右生成,因此不需要复制

我希望这些评论有助于解释逻辑。dp解决方案的速度足够快,以至于我找不到一个不会溢出很长时间的测试用例,这个时间超过了几毫秒;例如:

$ time ./coins dp 1000000 1 2 3 4 5 6 7
3563762607322787603

real    0m0.024s
user    0m0.012s
sys     0m0.012s

//返回从
//正整数容器的元素。
//注意:如果元素

//容器的结尾是@RamiJarrar
0,2,2
是什么意思?如果
数组={0,1,2,3,4}
,0,2,2不是结尾,它继续,我会编辑,@RamiJarrar仍然不知道它应该是什么意思。它是数组的一个组合,我生成所有组合,然后我检查哪一个组合是和W,但是我怎么才能从子集和问题中得到求和到W的组合数呢?这不是子集和问题的一个变种,除非OP数组中的整数可以是负数。公平地说,OP没有说“一个正整数数组”,但从他对如何解决问题的描述来看,这就是他的意思。示例中W因子的和在哪里|我看你的例子中没有提到W,你能在例子中引用它吗?
// Return the number of ways of generating the sum n from the
// elements of a container of positive integers.
// Note: This function will overflow the stack if an element
//       of the container is <= 0.
template<typename ITER>
long long count(int n, ITER begin, ITER end) {
  if (n == 0) return 1;
  else if (begin == end || n < 0) return 0;
  else return
      // combinations which don't use *begin
    count(n, begin + 1, end) +
      // use one (more) *begin.
    count(n - *begin, begin, end);
}

// Same thing, but uses O(n) storage and runs in O(n*|c|) time, 
// where |c| is the length of the container. This implementation falls
// directly out of the recursive one above, but processes the items
// in the reverse order; each time through the outer loop computes
// the combinations (for all possible sums <= n) for sum prefix of
// the container.
template<typename ITER>
long long count1(int n, ITER begin, ITER end) {
  std::vector<long long> v(n + 1, 0);
  v[0] = 1;
  // Initial state of v: v[0] is 1; v[i] is 0 for 1 <= i <= n.
  // Corresponds to the termination condition of the recursion.

  auto vbegin = v.begin();
  auto vend = v.end();
  for (auto here = begin; here != end; ++here) {
    int a = *here;
    if (a > 0 && a <= n) {
      auto in = vbegin;
      auto out = vbegin + a;
      // *in is count(n - a, begin, here).
      // *out is count(n, begin, here - 1).
      do *out++ += *in++; while (out != vend);
    }
  } 
  return v[n];
}