具有可用的面额集合的舍入金额

时间:2017-05-25 04:41:53

标签: c# algorithm

这不是基于单个值向上或向下舍入的常规舍入事物。 我想要一个函数,我将整数和面值的数量作为整数数组传递。 该函数应返回给我的是一个最接近的可能整数值,可通过传递的面额数来实现。 是否向上舍入或向下舍入将再次作为参数发送。

代码:

var amount = 61; // for. e.g.
int[] denoms = [20, 50]; // for. e.g.
bool roundUp = true;
amount = RoundAmount(amount, denoms, roundUp);

预期结果:

RoundAmount函数应该返回我已经通过的denom可实现的最接近的可能数量。

  1. 如果roundUp = true,则返回值应为70,因为70 = 20+50 金额70可以用一张20分和一张50分来表示。
  2. 如果roundUp = false,它应该已返回60,因为60 = 20+20+20和金额60可以通过3个20分的笔记来实现
  3. 到目前为止我得到了什么:

    我只能达到能够根据单个整数(而不是整数数组)向上或向下舍入数量的程度

    public int RoundAmount(int amount, int value, bool roundUp)
    {
       if (roundUp)
          amount = amount - (amount % value) + value;
       else
          amount = amount - (amount % value)
    
       return amount;
    }
    

    修改:

    我有另一个递归函数来检查金额是否可以实现, 只有在无法实现金额的情况下,才会调用RoundAmount函数。

    因此,在我的示例中,amount = 70永远不会成为输入,因为70可以通过可用的denoms实现,在这种情况下我不会调用RoundAmount

    解决方案:(感谢maraca和Koray)

    我很高兴它使用long数字,虽然这不是原始要求。

    private static long RoundAmount_maraca(long a, long[] d, bool up)
    {
        d = d.ToArray();
        Array.Sort(d);
    
        if (a < d[0])
            return up ? d[0] : 0;
        long count = 0;
        for (long i = 0; i < d.Length; i++)
        {
            if (d[i] == 0)
                continue;
            for (long j = i + 1; j < d.Length; j++)
                if (d[j] % d[i] == 0)
                    d[j] = 0;
            if (d[i] > a && !up)
                break;
            d[count++] = d[i];
            if (d[i] > a)
                break;
        }
        if (count == 1)
            return (!up ? a : (a + d[0] - 1)) / d[0] * d[0];
        long gcd = euclid(d[1], d[0]);
        for (long i = 2; i < count && gcd > 1; i++)
            gcd = euclid(d[i], gcd);
        if (up)
            a = (a + gcd - 1) / gcd;
        else
            a /= gcd;
        for (long i = 0; i < count; i++)
        {
            d[i] /= gcd;
            if (a % d[i] == 0)
                return a * gcd;
        }
        var set = new HashSet<long>();
        set.Add(0);
        long last = 0;
        for (long n = d[0]; ; n++)
        {
            if (!up && n > a)
                return last * gcd;
            for (long i = 0; i < count && n - d[i] >= 0; i++)
            {
                if (set.Contains(n - d[i]))
                {
                    if (n >= a)
                        return n * gcd;
                    if ((a - n) % d[0] == 0)
                        return a * gcd;
                    set.Add(n);
                    last = n;
                    break;
                }
            }
        }
    }
    private static long euclid(long a, long b)
    {
        while (b != 0)
        {
            long h = a % b;
            a = b;
            b = h;
        }
        return a;
    }
    

5 个答案:

答案 0 :(得分:4)

我假设您正在寻找具有相对少量面额b(例如少于100种面额)的高性能解决方案。虽然a金额和面额d[i]可能非常大(例如小于10 ^ 6)。

  1. 排序d升序并删除重复项。向下舍入时只保留小于或等于a的值,当向上舍入时,只保留大于或等于a的最小值,并丢弃较大的值。

  2. (可选)删除所有其他数字O(b ^ 2)的倍数。

  3. 计算面额的最大公约数gcd。您可以使用欧几里德算法从前两个数字开始,然后计算结果的最大公约数和第三个数字,依此类推。当然,你可以在到达时立即停止。

  4. a除以gcd,就像你要对结果进行舍入一样(使用整数除法,向下舍入:a /= gcd,向上舍入:a = (a + gcd - 1) / gcd) 。

  5. 将所有面额除以gcdd[i] /= gcd)。现在所有面额的最大公约数是1,因此保证存在Frobenius数(所有数量都大于该数,可以建立并且不需要舍入)。在执行此操作时,您还可以检查新值是否导致a % d[i] == 0,如果是,则立即返回a * gcd

  6. 为可以构建的值创建哈希set。它比数组更好,因为数组可能会浪费大量空间(请记住Frobenius数字)。在集合中添加零。

  7. 为当前数字创建变量n,使用最小面额进行初始化:n = d[0]

  8. 如果可以使用任何可用的面额构建n,换句话说set包含n - d[i]中的任何一个,则继续执行下一步。否则,将n增加一个并重复此步骤,除非n == a并且您正在向下舍入,然后您可以立即返回可以构建的最后一个数字乘以gcd。您也可以每次从n - d[b - 1]删除set,因为此值不再被请求。

  9. 如果n >= a返回n * gcd(在向上舍入时只能为true,则舍入将在步骤8中返回结果)。否则(a - n) % d[0] == 0返回a * gcd。这个检查甚至比查找Frobenius数字(可以构建d[0] - 1个连续值之后的数字)更好,它或多或少是等价的(d[0] - 1连续值表示其中一个之间的差异并且a modulo d[0]必须为零)但可以更快地返回。否则将n增加1并继续执行步骤8.

  10. d = {4,6}和a = 9999(或任何其他大奇数)的示例显示了该算法的优点。很容易看出奇数永远不会被建立,我们会用除了2之外的所有偶数来填满整个集合。但是如果我们除以gcd,我们得到d = {2,3}和aUp = 5000并且aDown = 4999 。{2,3}的Frobenius数是1(唯一不能建的数),所以在最多3个(覆盖所有模数的第一个数)步骤(而不是10000)之后,模数将为零,我们会返回一个* gcd,根据舍入方向给出9998或10000,这是正确的结果。

    这是包含测试的代码。我在我糟糕的笔记本上进行了六次跑步,花了90,92,108,94,96和101秒(编辑:如果当前面额大于当前数字&& n - d[i] >= 0的话,早期循环逃脱减半对于7200次随机舍入(每个方向3600次),并且使用不同面额(范围2到100),dMax(范围100到10 ^ 6)和dMax(范围100到10 ^ 6)的组合,给出平均约 45s 。 aMax(范围10 ^ 4到10 ^ 6),(参见底部代码的确切值)。我认为随机数生成和输出的时间可以忽略不计,因此使用此输入和给定范围,算法平均每秒 160个数字编辑:快三十倍)以下版本)。

    public static final int round(int a, int[] d, boolean up) {
        d = d.clone(); // otherwise input gets changed
        Arrays.sort(d);
        if (a < d[0])
            return up ? d[0] : 0;
        int count = 0;
        for (int i = 0; i < d.length; i++) {
            if (d[i] == 0)
                continue;
            for (int j = i + 1; j < d.length; j++)
                if (d[j] % d[i] == 0)
                    d[j] = 0;
            if (d[i] > a && !up)
                break;
            d[count++] = d[i];
            if (d[i] > a)
                break;
        }
        if (count == 1)
            return (!up ? a : (a + d[0] - 1)) / d[0] * d[0];
        int gcd = euclid(d[1], d[0]);
        for (int i = 2; i < count && gcd > 1; i++)
            gcd = euclid(d[i], gcd);
        if (up)
            a = (a + gcd - 1) / gcd;
        else
            a /= gcd;
        for (int i = 0; i < count; i++) {
            d[i] /= gcd;
            if (a % d[i] == 0)
                return a * gcd;
        }
        Set<Integer> set = new HashSet<>();
        set.add(0);
        int last = 0;
        for (int n = d[0];; n++) {
            if (!up && n > a)
                return last * gcd;
            for (int i = 0; i < count && n - d[i] >= 0; i++) {
                if (set.contains(n - d[i])) {
                    if (n >= a)
                        return n * gcd;
                    if ((a - n) % d[0] == 0)
                        return a * gcd;
                    set.add(n);
                    last = n;
                    break;
                }
            }
        }
    }
    
    public static final int euclid(int a, int b) {
        while (b != 0) {
            int h = a % b;
            a = b;
            b = h;
        }
        return a;
    }
    
    public static final int REPEAT = 100;
    public static final int[] D_COUNT = {2, 5, 10, 20, 50, 100};
    public static final int[] D_MAX = {100, 10000, 1000000};
    public static final int[] A_MAX = {10000, 1000000};
    
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        Random r = new Random();
        for (int i = 0; i < REPEAT; i++) {
            for (int j = 0; j < D_COUNT.length; j++) {
                for (int k = 0; k < D_MAX.length; k++) {
                    for (int l = 0; l < A_MAX.length; l++) {
                        int[] d = new int[D_COUNT[j]];
                        for (int m = 0; m < d.length; m++)
                            d[m] = r.nextInt(D_MAX[k]);
                        int a = r.nextInt(A_MAX[l]);
                        System.out.println(round(a, d, false));
                        System.out.println(round(a, d, true));
                    }
                }
            }
        }
        System.out.println((System.currentTimeMillis() - start) / 1000 + " seconds");
    }
    

    事实证明 @ Koray的编辑7对于给定的输入来说快了大约三倍(仅对于非常大的gcds,我上面的算法更快)。因此,要获得最终算法,我将算法的动态编程部分替换为@Koray (有一些改进)。它起作用,它比编辑7快大约十倍,并且比上面的算法快三十倍。这将给出 5000每秒舍入(非常粗略估计)。

    private static int round(int a, int[] d, boolean up) {
        d = d.clone();
        Arrays.sort(d);
        if (a < d[0])
            return up ? d[0] : 0;
        int count = 0;
        for (int i = 0; i < d.length; i++) {
            if (d[i] == 0)
                continue;
            if (a % d[i] == 0)
                return a;
            for (int j = i + 1; j < d.length; j++)
                if (d[j] > 0 && d[j] % d[i] == 0)
                    d[j] = 0;
            if (d[i] > a && !up)
                break;
            d[count++] = d[i];
            if (d[i] > a)
                break;
        }
        if (count == 1)
            return (!up ? a : (a + d[0] - 1)) / d[0] * d[0];
        int gcd = euclid(d[1], d[0]);
        for (int i = 2; i < count && gcd > 1; i++)
            gcd = euclid(d[i], gcd);
        if (gcd > 1) {
            if (up)
                a = (a + gcd - 1) / gcd;
            else
                a /= gcd;
            for (int i = 0; i < count; i++) {
                d[i] /= gcd;
                if (a % d[i] == 0)
                    return a * gcd;
            }
        }
        int best = !up ? d[count - 1] : ((a + d[0] - 1) / d[0] * d[0]);
        if (d[count - 1] > a) {
            if (d[count - 1] < best)
                best = d[count - 1];
            count--;
        }
        Stack<Integer> st = new Stack<Integer>();
        BitSet ba = new BitSet(a + 1);
        for (int i = 0; i < count; i++) {
            ba.set(d[i]);
            st.push(d[i]);
        }
        while (st.size() > 0) {
            int v1 = st.pop();
            for (int i = 0; i < count; i++) {
                int val = v1 + d[i];
                if (val <= a && !ba.get(val)) {
                    if ((a - val) % d[0] == 0)
                        return a * gcd;
                    ba.set(val, true);
                    st.push(val);
                    if (!up && val > best)
                        best = val;
                } else if (val > a) {
                    if (up && val < best)
                        best = val;
                    break;
                }
            }
        }
        return best * gcd;
    }
    

答案 1 :(得分:3)

我以简单的方式尝试过它。我希望我没有理解错误的问题,这不是太愚蠢:))

    private static void test()
    {
        var amount = 61;
        int[] denoms = new int[] { 20, 50 };
        int up = RoundAmount(amount, denoms, true);//->70
        int down = RoundAmount(amount, denoms, false);//->60
    }
    private static int RoundAmount(int amount, int[] denoms, bool roundUp)
    {   
        HashSet<int> hs = new HashSet<int>(denoms);
        bool added = true;
        while (added)
        {
            added = false;
            var arr = hs.ToArray();
            foreach (int v1 in arr)
                foreach (int v2 in arr)
                    if ((v1 < amount) && (v2 < amount) && (hs.Add(v1 + v2)))
                        added = true;
        }

        int retval = roundUp ? int.MaxValue : int.MinValue;
        foreach (int v in hs)
        {
            if (roundUp)
            {
                if ((v < retval) && (v >= amount))
                    retval = v;
            }
            else
            {
                if ((v > retval) && (v <= amount))
                    retval = v;
            }
        }
        return retval;
    }

如果此功能不符合要求或有问题,请通知我。在不知道原因的情况下进行投票让我感觉很糟糕。感谢。

编辑7 - 如果存在“0”denom,编辑6有一个错误。我详细检查了@ maraca的代码(我觉得它很棒),并且对此有所启发,我对代码进行了一些优化。以下是性能比较。 (我试图将maraca的代码转换为c#,我希望我做得对。)

private static int REPEAT = 100;
private static int[] D_COUNT = { 2, 5, 10, 20, 50, 100 };
private static int[] D_MAX = { 100, 10000, 1000000 };
private static int[] A_MAX = { 10000, 1000000 };
private static void testR()
{            
    Random r = new Random();
    long wMaraca = 0;
    long wKoray = 0;
    for (int i = 0; i < REPEAT; i++)
    {
        for (int j = 0; j < D_COUNT.Length; j++)
        {
            for (int k = 0; k < D_MAX.Length; k++)
            {
                for (int l = 0; l < A_MAX.Length; l++)
                {
                    int[] d = new int[D_COUNT[j]];
                    for (int m = 0; m < d.Length; m++)
                        d[m] = r.Next(D_MAX[k]);
                    int a = r.Next(A_MAX[l]);

                    Stopwatch maraca = Stopwatch.StartNew();
                    int m1 = RoundAmount_maraca(a, d, false);
                    int m2 = RoundAmount_maraca(a, d, true);
                    maraca.Stop();
                    wMaraca += maraca.ElapsedMilliseconds;

                    Stopwatch koray = Stopwatch.StartNew();
                    int k1 = RoundAmount_koray(a, d, false);
                    int k2 = RoundAmount_koray(a, d, true);
                    koray.Stop();
                    wKoray += koray.ElapsedMilliseconds;

                    if ((m1 != k1) || (m2 != k2))
                    {
                        throw new Exception("something is wrong!");
                    }
                }
            }
        }
    }

    //some results with debug compile
    //try1
    //maraca: 50757 msec
    //koray:  19188 msec
    //try2
    //maraca: 52623 msec
    //koray:  19102 msec
    //try3
    //maraca: 57139 msec
    //koray:  18952 msec
    //try4
    //maraca: 64911 msec
    //koray:  21070 msec
}
private static int RoundAmount_koray(int amount, int[] denoms, bool roundUp)
{
    List<int> lst = denoms.ToList();
    lst.Sort();
    if (amount < lst[0])
        return roundUp ? lst[0] : 0;
    HashSet<int> hs = new HashSet<int>();
    for (int i = 0, count = lst.Count; i < count; i++)
    {
        int v = lst[i];
        if (v != 0)
        {
            if (v > amount && !roundUp)
                break;
            if (hs.Add(v))
            {
                if (amount % v == 0)
                    return amount;
                else
                    for (int j = i + 1; j < count; j++)
                        if (lst[j] != 0)
                            if (v % lst[j] == 0)
                                lst[j] = 0;
                            else if (amount % (v + lst[j]) == 0)
                                return amount;
            }
        }
    }
    denoms = hs.ToArray();           

    HashSet<int> hsOK = new HashSet<int>(denoms);
    Stack<int> st = new Stack<int>(denoms);
    BitArray ba = new BitArray(amount + denoms.Max() * 2 + 1);
    int minOK = amount - denoms.Min();
    while (st.Count > 0)
    {
        int v1 = st.Pop();
        foreach (int v2 in denoms)
        {
            int val = v1 + v2;
            if (!ba.Get(val))
            {
                if (amount % val == 0)
                    return amount;
                ba.Set(val, true);
                if (val < amount)
                    st.Push(val);
                if (val >= minOK)
                    hsOK.Add(val);
            }
        }
    }

    if (!roundUp)
    {
        int retval = 0;
        foreach (int v in hsOK)
            if (v > retval && v <= amount)
                retval = v;
        return retval;
    }
    else
    {
        int retval = int.MaxValue;
        foreach (int v in hsOK)
            if (v < retval && v >= amount)
                retval = v;
        return retval;
    }
}
private static int RoundAmount_maraca(int a, int[] d, bool up)
{
    d = d.ToArray();
    Array.Sort(d);

    if (a < d[0])
        return up ? d[0] : 0;
    int count = 0;
    for (int i = 0; i < d.Length; i++)
    {
        if (d[i] == 0)
            continue;
        for (int j = i + 1; j < d.Length; j++)
            if (d[j] % d[i] == 0)
                d[j] = 0;
        if (d[i] > a && !up)
            break;
        d[count++] = d[i];
        if (d[i] > a)
            break;
    }
    if (count == 1)
        return (!up ? a : (a + d[0] - 1)) / d[0] * d[0];
    int gcd = euclid(d[1], d[0]);
    for (int i = 2; i < count && gcd > 1; i++)
        gcd = euclid(d[i], gcd);
    if (up)
        a = (a + gcd - 1) / gcd;
    else
        a /= gcd;
    for (int i = 0; i < count; i++)
    {
        d[i] /= gcd;
        if (a % d[i] == 0)
            return a * gcd;
    }
    var set = new HashSet<int>();
    set.Add(0);
    int last = 0;
    for (int n = d[0]; ; n++)
    {
        if (!up && n > a)
            return last * gcd;
        for (int i = 0; i < count && n - d[i] >= 0; i++)
        {
            if (set.Contains(n - d[i]))
            {
                if (n >= a)
                    return n * gcd;
                if ((a - n) % d[0] == 0)
                    return a * gcd;
                set.Add(n);
                last = n;
                break;
            }
        }
    }
}
private static int euclid(int a, int b)
{
    while (b != 0)
    {
        int h = a % b;
        a = b;
        b = h;
    }
    return a;
}

编辑 - c#中的Maraca

Maraca的最后编辑明显优于所有!我试图准备一个更好的c#转换他的代码+添加了一个ulong版本。 (int版本比ulong版本快〜1.6倍)

#region maraca int
private static int RoundAmount_maraca(int a, int[] d0, bool up)
{
    int[] d = new int[d0.Length];
    Buffer.BlockCopy(d0, 0, d, 0, d.Length * sizeof(int));
    Array.Sort(d);

    if (a < d[0])
        return up ? d[0] : 0;
    int count = 0;
    for (int i = 0; i < d.Length; i++)
    {
        if (d[i] == 0)
            continue;
        for (int j = i + 1; j < d.Length; j++)
            if (d[j] % d[i] == 0)
                d[j] = 0;
        if (d[i] > a && !up)
            break;
        d[count++] = d[i];
        if (d[i] > a)
            break;
    }
    if (count == 1)
        return (!up ? a : (a + d[0] - 1)) / d[0] * d[0];
    int gcd = euclid(d[1], d[0]);
    for (int i = 2; i < count && gcd > 1; i++)
        gcd = euclid(d[i], gcd);
    if (up)
        a = (a + gcd - 1) / gcd;
    else
        a /= gcd;
    for (int i = 0; i < count; i++)
    {
        d[i] /= gcd;
        if (a % d[i] == 0)
            return a * gcd;
    }
    int best = !up ? d[count - 1] : ((a + d[0] - 1) / d[0] * d[0]);
    if (d[count - 1] > a)
    {
        if (d[count - 1] < best)
            best = d[count - 1];
        count--;
    }
    var st = new Stack<int>();
    BitArray ba = new BitArray(a+1);
    for (int i = 0; i < count; i++)
    {
        ba.Set(d[i], true);
        st.Push(d[i]);
    }
    while (st.Count > 0)
    {
        int v1 = st.Pop();
        for (int i = 0; i < count; i++)
        {
            int val = v1 + d[i];
            if (val <= a && !ba.Get(val))
            {
                if ((a - val) % d[0] == 0)
                    return a * gcd;
                ba.Set(val, true);
                st.Push(val);
                if (!up && val > best)
                    best = val;
            }
            else if (up && val > a && val < best)
                best = val;
        }
    }
    return best * gcd;
}
private static int euclid(int a, int b)
{
    while (b != 0)
    {
        int h = a % b;
        a = b;
        b = h;
    }
    return a;
}
#endregion
#region maraca ulong
private static ulong RoundAmount_maraca_ulong(ulong a, ulong[] d0, bool up)
{
    ulong[] d = new ulong[d0.Length];
    Buffer.BlockCopy(d0, 0, d, 0, d.Length * sizeof(ulong));
    Array.Sort(d);

    if (a < d[0])
        return up ? d[0] : 0ul;
    int count = 0;
    for (int i = 0; i < d.Length; i++)
    {
        if (d[i] == 0ul)
            continue;
        for (int j = i + 1; j < d.Length; j++)
            if (d[j] % d[i] == 0ul)
                d[j] = 0ul;
        if (d[i] > a && !up)
            break;
        d[count++] = d[i];
        if (d[i] > a)
            break;
    }
    if (count == 1)
        return (!up ? a : (a + d[0] - 1ul)) / d[0] * d[0];
    ulong gcd = euclid(d[1], d[0]);
    for (int i = 2; i < count && gcd > 1; i++)
        gcd = euclid(d[i], gcd);
    if (up)
        a = (a + gcd - 1ul) / gcd;
    else
        a /= gcd;
    for (int i = 0; i < count; i++)
    {
        d[i] /= gcd;
        if (a % d[i] == 0ul)
            return a * gcd;
    }
    ulong best = !up ? d[count - 1] : ((a + d[0] - 1ul) / d[0] * d[0]);
    if (d[count - 1] > a)
    {
        if (d[count - 1] < best)
            best = d[count - 1];
        count--;
    }
    var st = new Stack<ulong>();
    UlongBitArray ba = new UlongBitArray(a + 1ul);
    for (int i = 0; i < count; i++)
    {
        ba.Set(d[i], true);
        st.Push(d[i]);
    }
    while (st.Count > 0)
    {
        ulong v1 = st.Pop();
        for (int i = 0; i < count; i++)
        {
            ulong val = v1 + d[i];
            if (val <= a && !ba.Get(val))
            {
                if ((a - val) % d[0] == 0ul)
                    return a * gcd;
                ba.Set(val, true);
                st.Push(val);
                if (!up && val > best)
                    best = val;
            }
            else if (up && val > a && val < best)
                best = val;
        }
    }
    return best * gcd;
}
private static ulong euclid(ulong a, ulong b)
{
    while (b != 0)
    {
        ulong h = a % b;
        a = b;
        b = h;
    }
    return a;
}
class UlongBitArray
{
    ulong[] bits;
    public UlongBitArray(ulong length)
    {
        this.bits = new ulong[(length - 1ul) / 32ul + 1ul];
    }
    public bool Get(ulong index)
    {
        return (this.bits[index / 32ul] & (1ul << (int)(index % 32ul))) > 0ul;
    }
    public void Set(ulong index, bool val)
    {
        if (val)
            this.bits[index / 32ul] |= 1ul << (int)(index % 32ul);
        else
            this.bits[index / 32ul] &= ~(1ul << (int)(index % 32ul));
    }
}
#endregion

编辑8

我做了一些改进,并且在随机测试中表现优于@ maraca的最新更新:)如果您选择使用我的自定义堆栈类,请在发布模式下进行测量。 (这个自定义堆栈类在调试模式下当然要慢得多,但在重新模式下比.NET快5-15倍。在我使用.NET Stack类的测试中没有改变两者之间的性能比较,它只是一个额外的提升。 )

        private delegate int RoundAmountDelegate(int amount, int[] denoms, bool roundUp);
        private static int REPEAT = 100;
        private static int[] D_COUNT = { 2, 5, 10, 20, 50, 100 };
        private static int[] D_MAX = { 100, 10000, 1000000 };
        private static int[] A_MAX = { 10000, 1000000 };
        private static void testR()
        {            
#if DEBUG
            while (true)
#endif
            {
                Random r = new Random();
                long wT1 = 0; RoundAmountDelegate func1 = RoundAmount_maraca;
                long wT2 = 0; RoundAmountDelegate func2 = RoundAmount_koray;
                for (int i = 0; i < REPEAT; i++)
                {
                    for (int j = 0; j < D_COUNT.Length; j++)
                    {
                        for (int k = 0; k < D_MAX.Length; k++)
                        {
                            for (int l = 0; l < A_MAX.Length; l++)
                            {
                                int[] d = new int[D_COUNT[j]];
                                ulong[] dl = new ulong[D_COUNT[j]];
                                for (int m = 0; m < d.Length; m++)
                                {
                                    d[m] = r.Next(D_MAX[k]) + 1;
                                    dl[m] = (ulong)d[m];
                                }
                                int a = r.Next(A_MAX[l]);
                                ulong al = (ulong)a;

                                Stopwatch w1 = Stopwatch.StartNew();
                                int m1 = func1(a, d, false);
                                int m2 = func1(a, d, true);
                                w1.Stop();
                                wT1 += w1.ElapsedMilliseconds;

                                Stopwatch w2 = Stopwatch.StartNew();
                                int k1 = func2(a, d, false);
                                int k2 = func2(a, d, true);
                                w2.Stop();
                                wT2 += w2.ElapsedMilliseconds;

                                if ((m1 != k1) || (m2 != k2))
                                {
#if !DEBUG
                                    MessageBox.Show("error");
#else
                                    throw new Exception("something is wrong!");
#endif
                                }
                            }
                        }
                    }
                }

                //some results with release compile

                //maraca:                       1085 msec
                //koray(with .NET Stack<int>):   801 msec

                //maraca:                       1127 msec
                //koray(with .NET Stack<int>):   741 msec

                //maraca:                        989 msec
                //koray(with .NET Stack<int>):   736 msec

                //maraca:                        962 msec
                //koray(with .NET Stack<int>):   632 msec

                //-------------------------------------------
                //maraca:                     1045 msec
                //koray(with custom stack):    674 msec

                //maraca:                     1060 msec
                //koray(with custom stack):    606 msec

                //maraca:                     1175 msec
                //koray(with custom stack):    711 msec

                //maraca:                      878 msec
                //koray(with custom stack):    699 msec

#if !DEBUG
                MessageBox.Show(wT1 + "  " + wT2 + "  %" + (double)wT2 / (double)wT1 * 100d);
#endif
            }
        }

        #region Koray
        private static int RoundAmount_koray(int amount, int[] denoms, bool roundUp)
        {
            int[] sorted = new int[denoms.Length];
            Buffer.BlockCopy(denoms, 0, sorted, 0, sorted.Length * sizeof(int));
            Array.Sort(sorted);
            int minD = sorted[0];
            if (amount < minD)
                return roundUp ? minD : 0;
            HashSet<int> hs = new HashSet<int>();
            for (int i = 0, count = sorted.Length; i < count; i++)
            {
                int v = sorted[i];
                if (v != 0)
                {
                    if (!roundUp && v > amount)
                        break;
                    else if (hs.Add(v))
                    {
                        if (amount % v == 0)
                            return amount;
                        else
                            for (int j = i + 1; j < count; j++)
                                if (sorted[j] != 0)
                                    if (v % sorted[j] == 0)
                                        sorted[j] = 0;
                                    else if (amount % (v + sorted[j]) == 0)
                                        return amount;
                    }
                }
            }
            denoms = new int[hs.Count];
            int k = 0;
            foreach (var v in hs)
                denoms[k++] = v;

            HashSet<int> hsOK = new HashSet<int>(denoms);
            stack st = new stack(denoms);
            //Stack<int> st = new Stack<int>(denoms);
            BitArray ba = new BitArray(amount + denoms[denoms.Length - 1] * 2 + 1);
            int minOK = roundUp ? amount : amount - minD;
            int maxOK = roundUp ? amount + minD : amount;
            while (st.Count > 0)
            {
                int v1 = st.Pop();
                foreach (int v2 in denoms)
                {
                    int val = v1 + v2;
                    if (val <= maxOK)
                    {
                        if (!ba.Get(val))
                        {
                            if (amount % val == 0)
                                return amount;

                            int diff = amount - val;
                            if (diff % v1 == 0 || diff % v2 == 0)
                                return amount;

                            ba.Set(val, true);
                            if (val < amount)
                                st.Push(val);
                            if (val >= minOK)
                                hsOK.Add(val);
                        }
                    }
                    else
                        break;
                }
            }

            if (!roundUp)
            {
                int retval = 0;
                foreach (int v in hsOK)
                    if (v > retval && v <= amount)
                        retval = v;
                return retval;
            }
            else
            {
                int retval = int.MaxValue;
                foreach (int v in hsOK)
                    if (v < retval && v >= amount)
                        retval = v;
                return retval;
            }
        }
        private sealed class stack
        {
            int[] _array;
            public int Count;
            public stack()
            {
                this._array = new int[0];
            }
            public stack(int[] arr)
            {
                this.Count = arr.Length;
                this._array = new int[this.Count*2];
                Buffer.BlockCopy(arr, 0, this._array, 0, this.Count * sizeof(int));
            }
            public void Push(int item)
            {
                if (this.Count == this._array.Length)
                {
                    int[] destinationArray = new int[2 * this.Count];
                    Buffer.BlockCopy(this._array, 0, destinationArray, 0, this.Count * sizeof(int));
                    this._array = destinationArray;
                }
                this._array[this.Count++] = item;
            }
            public int Pop()
            {
                return this._array[--this.Count];
            }
        }
        #endregion
        #region Maraca
        private static int RoundAmount_maraca(int a, int[] d0, bool up)
        {
            int[] d = new int[d0.Length];
            Buffer.BlockCopy(d0, 0, d, 0, d.Length * sizeof(int));
            Array.Sort(d);

            if (a < d[0])
                return up ? d[0] : 0;
            int count = 0;
            for (int i = 0; i < d.Length; i++)
            {
                if (d[i] == 0)
                    continue;
                for (int j = i + 1; j < d.Length; j++)
                    if (d[j] % d[i] == 0)
                        d[j] = 0;
                if (d[i] > a && !up)
                    break;
                d[count++] = d[i];
                if (d[i] > a)
                    break;
            }
            if (count == 1)
                return (!up ? a : (a + d[0] - 1)) / d[0] * d[0];
            int gcd = euclid(d[1], d[0]);
            for (int i = 2; i < count && gcd > 1; i++)
                gcd = euclid(d[i], gcd);
            if (up)
                a = (a + gcd - 1) / gcd;
            else
                a /= gcd;
            for (int i = 0; i < count; i++)
            {
                d[i] /= gcd;
                if (a % d[i] == 0)
                    return a * gcd;
            }
            int best = !up ? d[count - 1] : ((a + d[0] - 1) / d[0] * d[0]);
            if (d[count - 1] > a)
            {
                if (d[count - 1] < best)
                    best = d[count - 1];
                count--;
            }
            var st = new Stack<int>();
            BitArray ba = new BitArray(a + 1);
            for (int i = 0; i < count; i++)
            {
                ba.Set(d[i], true);
                st.Push(d[i]);
            }
            while (st.Count > 0)
            {
                int v1 = st.Pop();
                for (int i = 0; i < count; i++)
                {
                    int val = v1 + d[i];
                    if (val <= a && !ba.Get(val))
                    {
                        if ((a - val) % d[0] == 0)
                            return a * gcd;
                        ba.Set(val, true);
                        st.Push(val);
                        if (!up && val > best)
                            best = val;
                    }
                    else if (up && val > a && val < best)
                        best = val;
                }
            }
            return best * gcd;
        }
        private static int euclid(int a, int b)
        {
            while (b != 0)
            {
                int h = a % b;
                a = b;
                b = h;
            }
            return a;
        }
        #endregion

答案 2 :(得分:2)

这是一个标准的背包问题,你可以谷歌它来参考其维基页面的概念。

我认为您的问题可以分为两部分。

为背包做背包。

使用f[i]表示用于构建金额i的最后一个面额,而f[i]==-1表示i无法获取。

fill f with -1
f[0] = 0
for i from 0 to target_amount + min(denoms) - 1
    for j from 0 to denoms.size()
        if f[i - denoms[j]] != -1
        {
            f[i] = denoms[j]
            break
        }

根据roundUp查找最近的金额。

  • roundUp == true

target_amount开始,升序找到f[i]不是-1

  • roundUp == false

target_amount开始,向后搜索f[i],而不是-1

可选:找出构建目标金额的面额

回溯您的f[target_amount]

答案 3 :(得分:1)

使用可能的硬币组合(标准动态编程问题)填充长度为amount + smallestdenomination + 1的数组。

然后从amount索引向四舍五入的方向走这个数组。

Delphi示例

 var
  A : array of Integer;
  Denoms: array of Integer;
  coin, amount, idx, i, Maxx: Integer;
  roundUp: Boolean;
  s: string;
begin
  amount := 29;
  SetLength(Denoms, 2);
  Denoms[0] := 7;
  Denoms[1] := 13;
  Maxx := amount + MinIntValue(Denoms);
  SetLength(A, Maxx + 1);
  A[0] := 1;

  for coin in Denoms do begin
    for i := 0 to Maxx - coin do
       if A[i] <> 0 then
          A[i + coin] := coin;
  end;

  roundUp := True;
  idx := amount;
  i := 2 * Ord(roundUp) - 1;// 1 for  roundUp=true, -1 for false
  while A[idx] = 0 do  //scan for nonzero entry
    idx := idx + i;

  s := '';
  while idx > 0 do begin     //roll back to get components of this sum
    s := s + Format('%d ', [A[idx]]);
    idx := idx - A[idx];
  end;
  Memo1.Lines.Add(s);

13 13 7roundUp := True;输出7 7 7 7组合。

(代码不寻求&#34;最佳&#34;解决方案)

硬币3和5的例子:

[0, 0, 0, 3, 0, 5, 3, 0, 5, 3, 5]

要查找制作单元格8的硬币,请按单元格值逐步降低:按5然后按3。

答案 4 :(得分:1)

Coin Problem是一个研究得很好的主题,我想参考一些你可能找到更好解决方案的论文:

此外,使用C#(静态类型语言)将限制您使用最有效的算法而非动态类型语言。如果您打算沿着这条路走下去,可以查看这个网站The Frobenius problem。你可以右键单击并检查代码(虽然我真的不太了解没有javascript经验)

无论如何,这就是我在C#中解决问题的方法:

private static List<int> _denominations = new List<int>() { 1000, 5000 };
private static int _denominationMin = _denominations[0];

static void Main()
{
    bool roundDown = false;

    Console.WriteLine("Enter number: ");
    int input = Convert.ToInt32(Console.ReadLine());

    if(roundDown)
    {
        for(int i = input; i > _denominationMin; i--)
        {
            if(Check(0,0,i))
            {
                Console.WriteLine("Number: {0}", i);
                break;
            }
        }
    }
    else
    {
        for (int i = input; i < int.MaxValue; i++)
        {
            if (Check(0, 0, i))
            {
                Console.WriteLine("Number: {0}", i);
                break;
            }
        }
    }
    Console.Read();
}

static bool Check(int highest, int sum, int goal)
{
    //Bingo!
    if (sum == goal)
    {
        return true;
    }
    //Oops! exceeded here
    if (sum > goal)
    {
        return false;
    }

    // Loop through _denominations.
    foreach (int value in _denominations)
    {
        // Add higher or equal amounts.
        if (value >= highest)
        {
            if(Check(value, sum + value, goal))
            {
                return true;
            }
        }
    }
    return false;
}

对于输入19999,{4,6}工作得很好,所以我认为不是那么糟糕。肯定有改进的余地,不会遇到Stackoverflow Exception。一个可以输入一半或四分之一。或者减去具有子集为面额的因子的数字。此外,重要的是将面额排序并且不包含另一个条目E.x的倍数。 {4,6,8} - &gt; {4,6}。

无论如何,如果我有时间,我会尽力提高效率。只是想提供一个替代解决方案。