给定从1到k的数字,请选择d个其和等于v的数字

时间:2019-04-16 08:14:30

标签: algorithm math sum

我正在尝试查找具有以下属性的集合中不同向量的数量:

  • 一组是从1到k + 1的k个数字
  • D是可以选择的元素数量
  • V是元素的总和

示例
k = 3,d = 3,v = 6,结果是7;
<1、2、3>,<1、3、2>,<2、1、3>,<2、2、2>,<2、3、1>,<3、1、2>,<3 ,2,1>

k = 4,d = 2,v = 7,结果是2;
<3,4>,<4,3>
在这种情况下,<2,5>无效,因为5超过了k的值。

我想找出是否有一个数学公式来计算结果。如果没有公式,该算法的执行效率如何?我发现了一个相当神秘的实现,但是我想知道是否可以改进它。

public static int NumberOfDistinctVectors(int k, int d ,int v) {
  if((v > k * d) || (v < d)) return 0;
  if(d == 1 || v == d) return 1;
  if(v == d + 1) return d;

  int alpha = 1, beta = 0;
  if(1 < v + k - k * d) 
    alpha = v + k - k * d;

  if(k < v - d + 1)
    beta = k;
  else
    beta = v - d + 1;

  int sum = 0;
  for(int i = alpha; i <= beta; i++) {
    sum += NumberOfDistinctVectors(k, d-1, v-i);
  }

  return sum;
}

2 个答案:

答案 0 :(得分:4)

该问题与以下内容非常相关:

  

b组中的c个相同对象分布在一起的组合数量是多少   没有哪个群组包含超过n个对象?

here中进行了讨论

仅考虑您的数字是由对象(+1)组成的。因此,就您而言

  • c = d,因为每个组对应于您的一个数字
  • b = v-d,因为您需要在d个组中至少放置一个(+1)对象
  • n = k-1,因为我们假设每个组中已经有(+1),并且不希望大于k

找到以下代码(将c(N,K)使用appache-commons

public static int NumberOfDistinctVectors(int k, int d ,int v) {

    return combinations(v-d, d, k-1);
}

//combinations to distribute b identical objects to c groups 
//where no group has more than n objects
public static int combinations(int b, int c, int n)
{
    int sum = 0;
    for(int i = 0; i <= c; i++)
    {
        if(b+c-1-i*(n+1) >= c-1)
            sum += Math.pow(-1, i) * CombinatoricsUtils.binomialCoefficient(c, i)
                   * CombinatoricsUtils.binomialCoefficient(b+c-1-i*(n+1), c-1);
    }
    return sum;
}

让我也引用原始答案:

  

”这实际上是否比复发更有用   另一个问题”

答案 1 :(得分:2)

这是另一种可能更有效的计数方法。它基于permutations with repetition的公式。我在代码中添加了注释,希望它使跟踪变得更容易。

public static int NumberOfDistinctVectors2(int k, int d, int v)
{
    return NumberOfDistinctVectors2_rec(1, 0, k, d, v, 1, 1);
}

public static int NumberOfDistinctVectors2_rec(
    int i,     /* Current number being added */
    int j,     /* Amount of already picked numbers */
    int k,     /* Maximum number that can be picked */
    int d,     /* Total amount of numbers to pick */
    int v,     /* Remaining value */
    long num,  /* Numerator in "permutations with repetition" formula */
    long den)  /* Denominator in "permutations with repetition" formula */
{
    // Amount of remaining numbers to pick
    int rem = d - j;
    // Remaining value is too big or too small
    if (v < i * rem || v > k * rem) return 0;
    // If no numbers to add then we are done
    if (rem == 0) return Math.toIntExact(num / den);
    // If only one number to add this can be used as a "shortcut"
    if (rem == 1) return d * Math.toIntExact(num / den);

    // Counted permutations
    int count = 0;
    // Maximum amount of repetitions for the current number
    int maxRep = Math.min(v / i, rem);
    // Factor to multiply the numerator
    int numFactor = 1;
    // Factor to multiply the denominator
    int denFactor = 1;
    // Consider adding repetitions of the current number
    for (int r = 1; r <= maxRep; r++)
    {
        // The numerator is the factorial of the total amount of numbers
        numFactor *= (j + r);
        // The denominator is the product of the factorials of the number of repetitions of each number
        denFactor *= r;
        // We add "r" repetitions of the current number and count all possible permutations from there
        count += NumberOfDistinctVectors2_rec(i + 1, j + r, k, d, v - i * r, num * numFactor, den * denFactor);
    }
    // Consider permutations that do not include the current number
    count += NumberOfDistinctVectors2_rec(i + 1, j, k, d, v, num, den);
    return count;
}

这里是一门小类测试它,这种方法看起来要快得多(see it in Rextester)。

class NumberOfDistinctVectorsTest
{
    // Original method
    public static int NumberOfDistinctVectors(int k, int d ,int v)
    {
        if((v > k * d) || (v < d)) return 0;
        if(d == 1 || v == d) return 1;
        if(v == d + 1) return d;

        int alpha = 1, beta = 0;
        if(1 < v + k - k * d) 
            alpha = v + k - k * d;

        if(k < v - d + 1)
            beta = k;
        else
            beta = v - d + 1;

        int sum = 0;
        for(int i = alpha; i <= beta; i++)
        {
            sum += NumberOfDistinctVectors(k, d-1, v-i);
        }

        return sum;
    }

    // New method
    public static int NumberOfDistinctVectors2(int k, int d, int v)
    {
        return NumberOfDistinctVectors2_rec(1, 0, k, d, v, 1, 1);
    }

    public static int NumberOfDistinctVectors2_rec(int i, int j, int k, int d, int v, long num, long den)
    {
        int rem = d - j;
        if (v < i * rem || v > k * rem) return 0;
        if (rem == 0) return Math.toIntExact(num / den);
        if (rem == 1) return d * Math.toIntExact(num / den);

        int count = 0;
        int maxRep = Math.min(v / i, rem);
        int numFactor = 1;
        int denFactor = 1;
        for (int r = 1; r <= maxRep; r++)
        {
            numFactor *= (j + r);
            denFactor *= r;
            count += NumberOfDistinctVectors2_rec(i + 1, j + r, k, d, v - i * r, num * numFactor, den * denFactor);
        }
        count += NumberOfDistinctVectors2_rec(i + 1, j, k, d, v, num, den);
        return count;
    }

    public static void main(final String[] args)
    {
        // Test 1
        System.out.println(NumberOfDistinctVectors(3, 3, 6));
        System.out.println(NumberOfDistinctVectors2(3, 3, 6));
        // Test 2
        System.out.println(NumberOfDistinctVectors(4, 2, 7));
        System.out.println(NumberOfDistinctVectors2(4, 2, 7));
        // Test 3
        System.out.println(NumberOfDistinctVectors(12, 5, 20));
        System.out.println(NumberOfDistinctVectors2(12, 5, 20));
        // Test runtime
        long startTime, endTime;
        int reps = 100;
        startTime = System.nanoTime();
        for (int i = 0; i < reps; i++)
        {
            NumberOfDistinctVectors(12, 5, 20);
        }
        endTime = System.nanoTime();
        double t1 = ((endTime - startTime) / (reps * 1000.));
        startTime = System.nanoTime();
        for (int i = 0; i < reps; i++)
        {
            NumberOfDistinctVectors2(12, 5, 20);
        }
        endTime = System.nanoTime();
        double t2 = ((endTime - startTime) / (reps * 1000.));
        System.out.println("Original method: " + t1 + "ms");
        System.out.println("New method: " + t2 + "ms");
    }
}

输出:

7
7
2
2
3701
3701
Original method: 45.64331ms
New method: 5.89364ms

编辑:包括JDoodle在内的新测试(使用Apache Commons 3.6.1在SaiBot's answer上运行):

import org.apache.commons.math3.util.CombinatoricsUtils;

public class NumberOfDistinctVectorsTest
{
    // Original method
    public static int NumberOfDistinctVectors(int k, int d ,int v)
    {
        if((v > k * d) || (v < d)) return 0;
        if(d == 1 || v == d) return 1;
        if(v == d + 1) return d;

        int alpha = 1, beta = 0;
        if(1 < v + k - k * d) 
            alpha = v + k - k * d;

        if(k < v - d + 1)
            beta = k;
        else
            beta = v - d + 1;

        int sum = 0;
        for(int i = alpha; i <= beta; i++)
        {
            sum += NumberOfDistinctVectors(k, d-1, v-i);
        }

        return sum;
    }

    // jdehesa method
    public static int NumberOfDistinctVectors2(int k, int d, int v)
    {
        return NumberOfDistinctVectors2_rec(1, 0, k, d, v, 1, 1);
    }

    public static int NumberOfDistinctVectors2_rec(int i, int j, int k, int d, int v, long num, long den)
    {
        int rem = d - j;
        if (v < i * rem || v > k * rem) return 0;
        if (rem == 0) return Math.toIntExact(num / den);
        if (rem == 1) return d * Math.toIntExact(num / den);

        int count = 0;
        int maxRep = Math.min(v / i, rem);
        int numFactor = 1;
        int denFactor = 1;
        for (int r = 1; r <= maxRep; r++)
        {
            numFactor *= (j + r);
            denFactor *= r;
            count += NumberOfDistinctVectors2_rec(i + 1, j + r, k, d, v - i * r, num * numFactor, den * denFactor);
        }
        count += NumberOfDistinctVectors2_rec(i + 1, j, k, d, v, num, den);
        return count;
    }

    // SaiBot method
    public static int NumberOfDistinctVectors3(int k, int d ,int v)
    {
        return combinations(v-d, d, k-1);
    }

    //combinations to distribute b identical objects to c groups 
    //where no group has more than n objects
    public static int combinations(int b, int c, int n)
    {
        int sum = 0;
        for(int i = 0; i <= c; i++)
        {
            if(b+c-1-i*(n+1) >= c-1)
                sum += Math.pow(-1, i) * CombinatoricsUtils.binomialCoefficient(c, i)
                       * CombinatoricsUtils.binomialCoefficient(b+c-1-i*(n+1), c-1);
        }
        return sum;
    }

    public static void main(final String[] args)
    {
        // Test 1
        System.out.println(NumberOfDistinctVectors(3, 3, 6));
        System.out.println(NumberOfDistinctVectors2(3, 3, 6));
        System.out.println(NumberOfDistinctVectors3(3, 3, 6));
        // Test 2
        System.out.println(NumberOfDistinctVectors(4, 2, 7));
        System.out.println(NumberOfDistinctVectors2(4, 2, 7));
        System.out.println(NumberOfDistinctVectors3(4, 2, 7));
        // Test 3
        System.out.println(NumberOfDistinctVectors(12, 5, 20));
        System.out.println(NumberOfDistinctVectors2(12, 5, 20));
        System.out.println(NumberOfDistinctVectors3(12, 5, 20));
        // Test runtime
        long startTime, endTime;
        int reps = 100;
        startTime = System.nanoTime();
        for (int i = 0; i < reps; i++)
        {
            NumberOfDistinctVectors(12, 5, 20);
        }
        endTime = System.nanoTime();
        double t1 = ((endTime - startTime) / (reps * 1000.));
        startTime = System.nanoTime();
        for (int i = 0; i < reps; i++)
        {
            NumberOfDistinctVectors2(12, 5, 20);
        }
        endTime = System.nanoTime();
        double t2 = ((endTime - startTime) / (reps * 1000.));
        startTime = System.nanoTime();
        for (int i = 0; i < reps; i++)
        {
            NumberOfDistinctVectors3(12, 5, 20);
        }
        endTime = System.nanoTime();
        double t3 = ((endTime - startTime) / (reps * 1000.));
        System.out.println("Original method: " + t1 + "ms");
        System.out.println("jdehesa method: " + t2 + "ms");
        System.out.println("SaiBot method: " + t3 + "ms");
    }
}

输出:

7
7
7
2
2
2
3701
3701
3701
Original method: 97.81325ms
jdehesa method: 7.2753ms
SaiBot method: 2.70861ms

在JDoodle中计时不是很稳定(之所以使用它是因为它允许Maven依赖),但总的来说,SaiBot的方法是最快的。