首页 > 技术文章 > 糖果传递

lToZvTe 2020-12-20 11:19 原文

糖果传递

题目描述

\(n\) 个小朋友坐成一圈,每人有 \(a_i\) 个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为 \(1\)

思路

​ 我们可以找到均分后第 \(i\) 个是缺牌,还是多牌,那么完成均分的代价就是多牌向缺牌的路径,因此怎样选择会是更优答案呢?

​ 如果我用前缀和 \(sum[i]\)来表示这 \(i\) 个人的手牌

​ 举个例子 \(i_3 = {1,0,-1}\), 显然 \(sum[i_3] = 0\) 有什么用呢?

​ 如果我在 \(-1\) 右边加一个 \(k\) ,那么

​ $sum[i_4] - sum[i_0] = k $ ,定义为 \(a\)

\(sum[i_4] - sum[i_1] = k - 1\),定义为 \(b\)

\(sum[i_4] - sum[i_2] = k - 1\),定义为 \(c\)

​ 则有 \(abs(a + c +b)\) 刚好为是价值,\(k\) 在左边同理

​ 那么 总价值就可以表示成:

\[\sum_{i=1}^{N}|Sum[i]-Sum[k]| \]

​ 要想价值最小,找 \(k\) 是关键!

​ 这里引入一个 “货仓选址” 问题,即你需要找出所有货仓到达你选择的距离之和最短,很显然,选择中位数是最优解,前缀和就可以搞定

​ 我们可以发现,这刚好和我们总价值的思路很想呀,对不对!

​ 那么最优解就是可以找到了~~

看代码

#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
#define int long long
inline int read() {
    int x = 0, f = 1;
    char ch = getchar();

    while (ch < '0' || ch > '9') {
        if (ch == '-')
            f = -1;

        ch = getchar();
    }

    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }

    return x * f;
}

const int manx = 1e7;
int n,a[manx],sum[manx],ave,ans;
 main(){
     n = read();
     for(int i = 1;i <= n; i++){
         a[i] = read();
         ave += a[i]; 
     }
     ave /= n;//平均值
     for(int i = 1;i <= n; i++) a[i] -= ave;
     for(int i = 1;i <= n; i++){
         sum[i] = sum[i - 1] + a[i];
     }
     sort(sum+1,sum+1+n);// 先排序
     int t = sum[(n+1)>>1];// 最好的K (中位数)
     for(int i = 1;i <= n; i++) ans += abs(t - sum[i]);
     cout<<ans;
 }

推荐阅读