给定一个整数数组,找到使每个值相等的最小步数

时间:2017-06-12 12:18:20

标签: algorithm sorting discrete-mathematics

我正在练习算法并遇到了这个问题。我无法解决它。我阅读了所提供的社论,但它没有解释解决方案背后的概念或想法。这是问题链接(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;
}

我正在寻找上述代码/逻辑的解释。它是如何给出最少的步骤?欢迎任何替代解决方案/方法(不一定是代码),但请解释您的方法。

如果有任何不明确的地方,请访问该链接或在评论部分询问。

2 个答案:

答案 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,我们简单地找到了一种平均分配苹果的方法。但是,这种通过苹果的方式不一定是最佳的。这就是为什么我们使用中位数技巧来找到最佳技巧。

我希望这会有所帮助。

相关问题