我正在练习算法并遇到了这个问题。我无法解决它。我阅读了所提供的社论,但它没有解释解决方案背后的概念或想法。这是问题链接(Question)。
问题陈述 - >
N 男生围成一圈。他们每个人手里都有一些苹果。您会发现苹果的总数可以除以 N 。所以你想要在所有男孩中平分苹果。但他们是如此懒惰,以至于他们每个人都只想一步给一个邻居一个苹果。计算使每个男孩拥有相同数量苹果的最小步数。
输入 - >第一行输入是整数 N ,第二行包含N个数字,表示 i 男孩的苹果数。
输出 - >使每个男孩拥有相同数量的苹果的最少步数。
以下是官方实施:
#include<bits/stdc++.h>
#define mod 10001
using namespace std;
typedef long long LL;
int main()
{
int n,a[10000],avg;
LL b[mod],val=0,s=0,m;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
s+=a[i];
}
avg=s/n;
b[0]=0;
for(int i = 0; i < n-1; i++){
b[i+1] = b[i]+a[i]-avg;
}
sort(b,b+n);
m = -b[n/2];
for(int i=0;i<n;i++)
{
val += abs(b[i]+m);
}
cout<<val;
return 0;
}
我正在寻找上述代码/逻辑的解释。它是如何给出最少的步骤?欢迎任何替代解决方案/方法(不一定是代码),但请解释您的方法。
如果有任何不明确的地方,请访问该链接或在评论部分询问。
答案 0 :(得分:2)
这是一个应该有用的代码注释版本,但基本上,这个想法是,由于每个步骤,只有一个苹果可以移动一个空间,所需的步骤数是需要移动的苹果数量+每个苹果需要移动的步数。这是为什么好的注释/ var名称很重要的一个例子,特别是当使用像这样的复杂算法时。
#include<bits/stdc++.h>
#define mod 10001
using namespace std;
typedef long long LL;
int main()
{
int n,apples[10000],avg;
LL b[mod],val=0,sum=0,median;
// Read number of boys
cin>>n;
for(int i=0;i<n;i++)
{
// Read i'th boy's # of apples
cin>>apples[i];
// And add to sum while while we are at it.
sum+=apples[i];
}
// Get average (desired apples per boy)
avg=sum/n;
// Clear value of first index
b[0]=0;
// Assuming only passing right,
// How many apples does boy[i] need to pass right?
// (Negative value means needs to pass left.)
for(int i = 0; i < n-1; i++){
// Apples this boy needs + needs to pass along
b[i+1] = b[i]+apples[i]-avg;
}
// Sort passes
sort(b,b+n);
// Take median, save as negative number
median = -b[n/2];
// Sum deference of all passes from the median.
// (negate steps where both boys needs are met by same pass)
for(int i=0;i<n;i++)
{
val += abs(b[i]+median);
}
cout<<val;
return 0;
}
这就是代码正在做的事情,但是其他人需要在另一个答案中添加正确性证明。
答案 1 :(得分:0)
这是我解释为什么算法正确的原因:
b
数组告诉我们第i
个男孩应该向右传递多少个苹果,以便他拥有平均数量的苹果(这是目标)。如果该值为负,则表示他需要将苹果向左传递。数组b
考虑到先前男孩传递的苹果。您可以这样想:如果第一个男孩在开始时的苹果数量超出平均水平,b[1]
告诉我们这个第一个男孩应该传递给右边的苹果数量。但是,如果他的数据少于开始时的平均值,则b[1]
告诉我们第二个男孩应该传给左边多少个苹果。在这种情况下,该值将为负。尽管索引1现在指向另一个人,但这并不重要,因为我们只对要传递的苹果总量感兴趣。在此之后,所有其他值b[i]
都会强制执行,只要我们认为男孩没有任何理由来回传递苹果。
事情来了:在上面我们已经看到,如果每个人都通过b[i]
数量的苹果,我们最终将得到每个人的平均数量。现在,如果每个人都通过b[i] + x
个苹果,而x
是一个任意数字怎么办?我们最终也会让每个人都拥有平均金额!为什么?因为在那种情况下,给定的人将向右传递x
个苹果,但另一方面,他将从左向他的那个人那里获取x
更多的苹果,因此最终他将拥有与每个人i
通过b[i]
苹果的苹果一样多。如果每个人i
都通过b[i]
个苹果,则通过的苹果总数将为abs(b[0]) + abs(b[1]) + … + abs(b[n-1])
。但是,我们知道,我们也可以通过让男孩为每个b[i] + x
传递x
个苹果来达到目标。我们的目标是使通过的苹果总数减少到最小。换句话说,我们的目标是使表达式abs(b[0] - x) + abs(b[1] - x) + … + abs(b[n-1] - x)
的值最小。因此,我们必须找到一个值x
,以使该总和最小。事实证明,数字b[i]
的中位数始终是最佳解决方案。这是为什么?现在考虑选择一个大于中位数的数字。在那种情况下,我们可以通过减小x
来使总和更小,因为当前数字的左边比当前数字的右边多,因为我们在中间数的右边。从中位数的定义得出。如果我们选择x
小于中位数,则情况是对称的。因此,中位数本身将始终是最佳解决方案。如果数字的数量为偶数,并且我们有两个中位数,则两者均将是最优值,并且两者之间也可以是任意数字。
要注意的一件事:我们开头分配给b[0]
的值根本没有关系!您可以将代码中的该行更改为b[0] = 2356235
;或任何您喜欢的东西,您将看到该程序运行正常。所有其他值将基于该值确定。您可以这样想。实际上,只要其他所有人都相应采取行动,第一人称多少苹果就可以了。通过选择b[0] = 0
,我们简单地找到了一种平均分配苹果的方法。但是,这种通过苹果的方式不一定是最佳的。这就是为什么我们使用中位数技巧来找到最佳技巧。
我希望这会有所帮助。