UVa 11300 Spreading the Wealth 分金币

本文介绍了一个关于金币在人群中均匀分配的数学问题。通过数学建模,将问题转化为寻找数轴上一点到多个固定点距离之和最小的问题,并给出了具体的算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:n个人坐成一圈,编号依次为1-n,1和n是邻座。每个人有一定数量的金币,保证金币总数能被n整除。现在要求每个人给相邻的两人各若干金币,使得所有人都持有等量金币。求所有人给出的金币的数量和的最小值。




这是一道数学题。我们这样考虑,对于某两个相邻的人,如果第一个人给了第二个人x个金币,而第二个人又给了第一个人y个金币,这相当于第一个人给了第二个人x-y个金币(当然,是负数也无妨,是负数相当于他得到金币)而第二个人没有给第一个人任何金币。如此看来,我们不妨设每个人只给他的下一个人金币(是负数就表示得到)。题目说了总数一定是n的倍数,则每个人最后的金币数为定值,不妨记为average。下面我们设每个人开始有Ai个金币,第i个人给第i + 1个人Xi个金币。我们可以列出方程:

A1 + X0 - X1 = average

A2 + X1 - X2 = average

A3 + X2 - X3 = average

......

An-1 + Xn-2 - Xn-1 = average

A0 + Xn-1 - X0 = average

当然最后这个方程并没有用,因为前n - 1个方程相加就可以得到这个方程


但是我们可以把每个Xi都用X1表示出来


下面对上述方程进行形如Xi = f(X1)形式的变形,可以得到:


X1 = X0 + A1 - average

X2 = X1 + A2 - average = X0 + A1 + A2 - 2*average

X3 = X2 + A3 - average = X0 + A1 + A2 - 3*average

......

Xn-1 = Xn-2 + An-1 - average = X0 + A1 + A2 + ...... + An-1 - average


若定义一个S数列,其中S0 = 0,Si = Si-1 + average - Ai,则有Si = X0 + A1 + A2 + ...... + Ai - i*average

代入上述变形即可得到

Xi = X0 - Si

于是我们要求的即为sigma(Xi) = sigma(X0 - Si)

这个式子的几何意义非常明显,就是给定数轴上的若干点S0、S1、S2 ...... Sn-1,要找一个点到这些点的距离和最小。

找这个点很简单。我们随便找个点x,设给定的若干点里位于x左边的点有L个,位于右边的点有R个,不放假设L > R,那么我们将x往左移动d个距离(不跨过x左边第一个点),则总距离和减少了L*d,增加了R*d,而L*d > R*d,所以移动后的点x更优。当L < R的时候亦是如此。因此我们必须保证对于找到的点x,位于x左边的给定的点和位于x右边的给定的点一样多。

于是我们将S0 ~ Sn-1排序后,要找的这个点一定是中间点(若总数是奇数则是正中间那个点,若总数是偶数则是中间两个点之间的任何一个点)。于是我们取这个点为Sn/2即可(对于总数是奇数或偶数都适用)。



#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
#define ll long long
using namespace std;

const int MAX = 1000005;
int n;
ll money[MAX], sum[MAX];

void input()
{
    for(int i = 0; i < n; i++)
        scanf("%d", &money[i]);
}

void solve()
{
    ll average = 0;
    for(int i = 0; i < n; i++)
        average += money[i];
    average /= n;
    sum[0] = 0;
    for(int i = 1; i < n; i++)
        sum[i] = sum[i - 1] + average - money[i];
    sort(sum, sum + n);
    int mid = n/2;
    ll ans = 0;
    for(int i = 0; i < n; i++)
        ans += labs(sum[mid] - sum[i]);
    printf("%lld\n", ans);
}

int main()
{
    while(scanf("%d", &n) != EOF)
    {
        input();
        solve();
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值