我试图用动态编程表填充方法来解决一个优化问题,但是我无法在最佳子结构中解决这个问题。
这是问题所在
给出一个从种子元素开始的排序数组,您可以将数组缩小为只有一个元素,可以移动多少步。
例如,这是排序后的数组
3, 20, 50, 100, 400
第一个元素是种子。
第一个元素还不能使用下一个更大的元素,因此我们需要添加它。根据约束条件,我们可以在第一步中添加少于种子的任何东西。因此,假设我们添加2。
moves = 1
seed = seed + 2
5, 20, 50, 100, 400
静止种子不能消耗下一个元素,所以我们加4
moves = 2
seed = seed + 4
9, 20, 50, 100, 400
moves = 3
seed = seed + 8
17, 20, 50, 100, 400
moves = 4
seed = seed + 16
33, 20, 50, 100, 400
现在种子可以“消耗”下一个元素 这样减少数组就变成
53 (33 + 20) , 50, 100 , 400
53可以消耗50并变为103
103, 100, 400
103可以容纳100并成为203
203, 400
203是种子,不能消耗400 所以
moves = 5
seed = seed + 202
405, 400
现在它可以消耗并变成805
我们也可以简单地删除400,这将算作1步。
总移动次数= 5
但是还有另一种解决方案,即从阵列中删除所有元素(20、50、100、400),这将花费4步。
因此,在这种情况下,最小移动数为4。
尝试从动态编程的角度来思考它,我可以看到在每一步中我们都有2个选择,无论是消耗元素还是删除元素。看来,要考虑的路径总数是所有2 ^ n条路径。
但是我无法将其分解为重叠的子问题或最佳子结构,甚至无法定义递归关系。
动态编程在这里正确吗?
一些观察:
答案 0 :(得分:1)
您可以使用(max {given array} +1)* array_size时间和内存复杂度来解决此问题。
int dp[array_size][max{given array}+1]={INF} // initially all index holds infinity
int seed=array[seed_index];
int Max=max{given array}+1;
dp[0][seed]=0;
for(int i=1;i<=n;i++){ // Consuming or removing ith element
for(int j=1;j<=Max;j++){ // if the current seed value is j
//Consider the removing case
dp[i][j]=min(dp[i][j],dp[i-1][j]+1);
// Increasing seed value
dp[i-1][min(Max,j+(j-1))]=min(dp[i-1][min(Max,j+(j-1))],dp[i-1][j]+1);
// Consider the consuming case
if(j>array[i]){
dp[i][min(Max,j+array[i])]=min(dp[i-1][j],dp[i][min(Max,j+array[i])]); // If j is the current seed value and you want to consume ith element then after consuming the ith element the seed will be j+array[i] but if it is greater than Max we can take upto max . Because for consuming the maximum value we need only the Max value.
}
}
}
// Now check the smallest possible value for the latest number.
result = INF;
for(int i=0;i<=Max;i++)result=min(result,dp[n][i]);
在这里dp [i] [j]表示将数组的第i个值减少至第i个值的结果,在将第i个值减少至第i个值后,种子将为j。 dp [i] [j]将保留将种子数为j的第i个值减少到1个元素的移动次数。
在消耗情况下:
如果j是当前种子值,并且您要使用第ith个元素,那么在使用第i个元素之后,种子将为j + array [i],但是如果它大于Max,则可以取最大Max。因为要消耗最大值,我们只需要max {given array}的maximum + 1。
递归方法:
int dp[array_size][max{given array}+1]={-1} // initially all index holds -1
int Max=max{givenarray}+1;
int DP(int seed,int index){
if(index==n+1)return 0;
if(dp[seed][index]!=-1)return dp[seed][index];// previously calculated
int res=inf;
// By removing
res=DP(seed,i+1)+1;
//Increasing seed
if(seed<Max)
res=min(res,1+DP(min(Max,seed+seed-1),index));
// By consuming
if(seed>array[i])
res=min(res,DP(seed+array[i],i+1));
dp[seed][index]=res;
return res;
}
答案 1 :(得分:1)
我不认为这是DP问题。
您可以删除最小元素a
或将其消耗掉(以及其他所有内容;您的第二次观察是正确的)。
移除将进行n
个动作,消耗将进行log (a/s)
个动作(此动作有效地使种子加倍)。做更少的事情;如果您选择增加种子并消耗,请对下一个元素应用相同的逻辑。
答案 2 :(得分:1)
观察到目标消费之间的去除没有意义,因为给定了种子s
和下一个元素; a
,b
和c
;为了能够接下来消费b
或c
,我们必须先增加s
到a
之后再增加b
。但是,如果我们利用“增加”动作将s
增加了那么远,那么它只能为我们节省(并且永远不会增加动作)消耗a
和b
来消耗{ {1}}。 (我认为您得出了类似的结论。)
因此,我们从左到右遍历的决定始终是是否停止增加种子并从此点到结束删除所有内容。正如已经提到的,我们可以通过观察给定的种子c
和下一个元素O(1)
来计算s
中任何需要增加的移动量:
a
总遍历为O(n)。