Algorithm 需要帮助了解珠宝Topcoder解决方案的解决方案吗

Algorithm 需要帮助了解珠宝Topcoder解决方案的解决方案吗,algorithm,dynamic-programming,Algorithm,Dynamic Programming,我对动态规划相当陌生,还不了解它能解决的大多数类型的问题。因此,我在理解这一点上面临着一些问题 至少有人能给我一些关于代码在做什么的提示吗 最重要的是,这个问题是?的一个变体,因为我正在研究这个问题,以理解这个问题 这两个函数实际上算什么为什么我们实际使用两个DP表? void cnk() { nk[0][0]=1; FOR(k,1,MAXN) { nk[0][k]=0; } FOR(n,1,MAXN) { nk[n][0]=1; FOR(k

我对动态规划相当陌生,还不了解它能解决的大多数类型的问题。因此,我在理解这一点上面临着一些问题

至少有人能给我一些关于代码在做什么的提示吗

最重要的是,这个问题是?的一个变体,因为我正在研究这个问题,以理解这个问题

这两个函数实际上算什么为什么我们实际使用两个DP表?

void cnk() {
  nk[0][0]=1;
  FOR(k,1,MAXN) {
    nk[0][k]=0;
  }  

  FOR(n,1,MAXN) {
      nk[n][0]=1;
      FOR(k,1,MAXN) 
        nk[n][k] = nk[n-1][k-1]+nk[n-1][k];
     }
}

void calc(LL T[MAXN+1][MAX+1]) {
  T[0][0] = 1;
  FOR(x,1,MAX) T[0][x]=0;
  FOR(ile,1,n) {
    int a = v[ile-1];
    FOR(x,0,MAX) {
      T[ile][x] = T[ile-1][x];
      if(x>=a) T[ile][x] +=T[ile-1][x-a];
    }
  }
}
如何使用以下逻辑构造原始解决方案

FOR(u,1,c) {
  int uu = u * v[done];
  FOR(x,uu,MAX)
  res += B[done][x-uu] * F[n-done-u][x] * nk[c][u];
  }
done=p;
}

<>任何帮助都将不胜感激。

让我们先考虑以下任务:

  • “给定一个由N个小于K的正整数组成的向量V,求其和等于S的子集数”
这可以通过使用一些额外内存的动态规划在多项式时间内解决

动态规划方法如下所示: 我们将解决以下形式的所有问题,而不是解决N和S的问题:

  • “找到写入总和s的方法数(使用s≤ S) 仅使用前n个≤ N个数字”
这是动态规划解决方案的一个共同特征:您解决的不是原来的问题,而是一系列相关的问题。关键思想是,更难的问题设置(即更高的n和s)的解决方案可以从更容易的设置的解决方案有效地建立起来

解决n=0的问题很简单(sum s=0可以用一种方式表示——使用空集,而所有其他的和不能用任何方式表示)。 现在考虑我们已经解决了所有值到某个n的问题,并且我们在矩阵A中有这些解(即[n] [s]是用第一个n个元素写和的方法的数目)。 然后,我们可以使用以下公式找到n+1的解:

A[n+1][s]=A[n][s-V[n+1]]+A[n][s]

事实上,当我们使用前n+1个数字写总和s时,我们可以包含或不包含V[n+1](n+1项)

这是
calc
函数计算的内容。(cnk函数使用Pascal规则计算二项式系数)

注:一般来说,如果我们最终只想回答初始问题(即N和S),那么数组
A
可以是一维的(长度为S)——这是因为每当试图构造N+1的解时,我们只需要N的解,而不需要较小值的解)


这个问题(这个答案中最初提到的问题)确实与(寻找和为零的元素子集)有关

如果我们对所用整数的绝对值有一个合理的限制(我们需要分配一个辅助数组来表示所有可能的可达和),则可以应用类似类型的动态规划方法

在零和问题中,我们实际上对计数不感兴趣,因此数组可以是布尔数组(指示和是否可达)

此外,如果存在一个辅助阵列,则可以使用另一个辅助阵列B来允许重构该解决方案

现在重复出现的情况如下所示:

if (!A[s] && A[s - V[n+1]]) {
    A[s] = true;

    // the index of the last value used to reach sum _s_,
    // allows going backwards to reproduce the entire solution
    B[s] = n + 1; 
}
注意:实际实现需要额外注意处理负数和,负数和不能直接表示数组中的索引(可以通过考虑最小可达和来移动索引,或者,如果在C/C++中工作,可以应用此答案中描述的技巧:)


我将在问题中详细说明上述想法如何应用于及其关联

B和F矩阵。

首先,注意解决方案中B和F矩阵的含义:

  • B[i][s]表示仅使用最小的i项达到总和s的方法的数量

  • F[i][s]表示仅使用最大的i项达到总和s的方法的数量

实际上,这两个矩阵都是在按升序(对于B)和降序(对于F)对珠宝值数组进行排序后,使用
calc
函数计算的

无重复案例的解决方案。

首先考虑没有重复珠宝价值的情况,使用以下示例:
[5,6,7,11,15]

为了提醒您答案,我将假设数组是按升序排序的(因此“第一个I项”将指最小的I项)

给Bob的每个项目的值都小于(或等于)给Frank的每个项目,因此在每个好的解决方案中都会有一个分离点,Bob只在该分离点之前接收项目,Frank只在该点之后接收项目

为了计算所有的解,我们需要对所有可能的分离点求和

例如,当分隔点位于第3项和第4项之间时,Bob将仅从
[5,6,7]
子数组(最小的3项)中拾取项,Frank将从剩余的
[11,12]
子数组(最大的2项)中拾取项。在这种情况下,两者都可以得到一个和(s=11)。每次两者都可以得到一个和,我们需要乘以它们各自可以达到的和的方式的数量(例如,如果Bob可以用4种方式达到一个和s,Frank可以用5种方式达到相同的和s,那么我们可以用该和得到20=4*5个有效解,因为每个组合都是有效解)

因此,通过考虑所有分离点和所有可能的和,我们将得到以下代码:

res = 0;
for (int i = 0; i < n; i++) {
    for (int s = 0; s <= maxS; s++) {
        res += B[i][s] * F[n-i][s]
    }
}
这与TopCoder上的解决方案越来越接近(在该解决方案中,
done
对应于上面的
i
,并且
uu=v[i]

dupl时情况的扩展
res = 0;
for (int i = 0; i < n; i++) {
    for (int s = v[i]; s <= maxS; s++) {
        res += B[i][s - v[i]] * F[n - 1 - i][s];
    }
}
import java.util.*;
public class Jewelry {
    int MAX_SUM=30005;
    int MAX_N=30;
    long[][] C;

    // Generate all possible sums
    // ret[i][sum] = number of ways to compute sum using the first i numbers from val[]
    public long[][] genDP(int[] val) {
        int i, sum, n=val.length;
        long[][] ret = new long[MAX_N+1][MAX_SUM];
        ret[0][0] = 1;
        for(i=0; i+1<=n; i++) {
            for(sum=0; sum<MAX_SUM; sum++) {
                // Carry over the sum from i to i+1 for each sum
                // Problem definition allows excluding numbers from calculating sums
                // So we are essentially excluding the last number for this calculation
                ret[i+1][sum] = ret[i][sum];

                // DP: (Number of ways to generate sum using i+1 numbers = 
                //         Number of ways to generate sum-val[i] using i numbers)
                if(sum>=val[i])
                    ret[i+1][sum] += ret[i][sum-val[i]];
            }
        }
        return ret;
    }

    // C(n, r) - all possible combinations of choosing r numbers from n numbers
    // Leverage Pascal's polynomial co-efficients for an n-degree polynomial
    // Leverage Dynamic Programming to build this upfront
    public void nCr() {
        C = new long[MAX_N+1][MAX_N+1]; 
        int n, r;
        C[0][0] = 1;
        for(n=1; n<=MAX_N; n++) {
            C[n][0] = 1;
            for(r=1; r<=MAX_N; r++)
                C[n][r] = C[n-1][r-1] + C[n-1][r]; 
         }    
    }

    /*  
        General Concept: 
            - Sort array 
            - Incrementally divide array into two partitions
               + Accomplished by using two different arrays - L for left, R for right 
            - Take all possible sums on the left side and match with all possible sums
              on the right side (multiply these numbers to get totals for each sum)
            - Adjust for common sums so as to not overcount
            - Adjust for duplicate numbers
    */
    public long howMany(int[] values) {
        int i, j, sum, n=values.length;

        // Pre-compute C(n,r) and store in C[][]
        nCr();

        /* 
            Incrementally split the array and calculate sums on either side
            For eg. if val={2, 3, 4, 5, 9}, we would partition this as 
            {2 | 3, 4, 5, 9} then {2, 3 | 4, 5, 9}, etc.  
            First, sort it ascendingly and generate its sum matrix L
            Then, sort it descendingly, and generate another sum matrix R
            In later calculations, manipulate indexes to simulate the partitions
            So at any point L[i] would correspond to R[n-i-1]. eg. L[1] = R[5-1-1]=R[3]
        */

        // Sort ascendingly
        Arrays.sort(values);

        // Generate all sums for the "Left" partition using the sorted array
        long[][] L = genDP(values);

        // Sort descendingly by reversing the existing array.
        // Java 8 doesn't support Arrays.sort for primitive int types
        // Use Comparator or sort manually. This uses the manual sort.
        for(i=0; i<n/2; i++) { 
            int tmp = values[i];
            values[i] = values[n-i-1];
            values[n-i-1] = tmp;
        }

        // Generate all sums for the "Right" partition using the re-sorted array
        long[][] R = genDP(values);

        // Re-sort in ascending order as we will be using values[] as reference later 
        Arrays.sort(values);

        long tot = 0;
        for(i=0; i<n; i++) {
            int dup=0;

            // How many duplicates of values[i] do we have?
            for(j=0; j<n; j++)
                if(values[j] == values[i]) 
                    dup++;
        /*
            Calculate total by iterating through each sum and multiplying counts on
            both partitions for that sum
            However, there may be count of sums that get duplicated
            For instance, if val={2, 3, 4, 5, 9}, you'd get:
                {2, 3 | 4, 5, 9} and {2, 3, 4 | 5, 9}  (on two different iterations) 
            In this case, the subset {2, 3 | 5} is counted twice
            To account for this, exclude the current largest number, val[i], from L's
            sum and exclude it from R's i index
            There is another issue of duplicate numbers
            Eg. If values={2, 3, 3, 3, 4}, how do you know which 3 went to L? 
            To solve this, group the same numbers
            Applying to {2, 3, 3, 3, 4} :
                - Exclude 3, 6 (3+3) and 9 (3+3+3) from L's sum calculation
                - Exclude 1, 2 and 3 from R's index count 
            We're essentially saying that we will exclude the sum contribution of these
            elements to L and ignore their count contribution to R
        */

            for(j=1; j<=dup; j++) {
                int dup_sum = j*values[i];
                for(sum=dup_sum; sum<MAX_SUM; sum++) {
                    // (ways to pick j numbers from dup) * (ways to get sum-dup_sum from i numbers) * (ways to get sum from n-i-j numbers)
                    if(n-i-j>=0)
                        tot += C[dup][j] * L[i][sum-dup_sum] * R[n-i-j][sum];
                }
            }

            // Skip past the duplicates of values[i] that we've now accounted for
            i += dup-1;
        }
        return tot;
    }
}