Algorithm 需要帮助了解珠宝Topcoder解决方案的解决方案吗
我对动态规划相当陌生,还不了解它能解决的大多数类型的问题。因此,我在理解这一点上面临着一些问题 至少有人能给我一些关于代码在做什么的提示吗 最重要的是,这个问题是?的一个变体,因为我正在研究这个问题,以理解这个问题 这两个函数实际上算什么为什么我们实际使用两个DP表?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
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的子集数”
- “找到写入总和s的方法数(使用s≤ S) 仅使用前n个≤ N个数字”
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的方法的数量
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;
}
}