122. 糖果传递(均分纸牌加强版)

有n个小朋友坐成一圈,每人有a[i]个糖果。

每人只能给左右两人传递糖果。

每人每次传递一个糖果代价为1。

求使所有人获得均等糖果的最小代价。

输入格式
第一行输入一个正整数n,表示小朋友的个数。

接下来n行,每行一个整数a[i],表示第i个小朋友初始得到的糖果的颗数。

输出格式
输出一个整数,表示最小代价。

数据范围
1≤n≤1000000
数据保证一定有解。

输入样例:

4
1
2
5
4

输出样例:

4

思路分析

在这里插入图片描述
设糖果平均数为a,ai代表第i堆糖果的个数,xi代表第i堆糖果向第i+1堆糖果(第n堆向第1堆传递)传递的糖果数目。温馨提示:xi代表是传递的差值(有很多种传递方案的)。

a1 - x1 + xn= a
a2 - x2 + x1 = a (含义:第1堆糖果向第2堆糖果传递了x1颗,第2堆糖果向第3堆糖果传递了x2颗,传递后第2堆糖果的数目等于糖果的平均数a)

an-1 - xn-1 + xn-2 = a
an - xn + xn-1 = a
∵有上面方程组等价于
x1 - xn=a1 - a
x2 - x1 = a2 - a

xn-1 - xn-2 = an-1 - a
xn + xn-1 =an - a
上面所有式子相加得0=0恒成立,所以上式中至少有一个多余方程。
∴令x1为自由变量,用x1表示其他的变量。
x1 = x1 + 0
x2= x1 + a2 - a
x3= x1 + a3 + a2 - 2a(由x3= x2 + a3 - a转化而来)

xn-1= x1 + an-1 + an-2 + … + a2 - (n-2)a
xn =x1 + an + an-1 + an-2 + … + a2 - (n-1)a
因为加法不好观察规律,所以将上式化为减法
x1 = x1 - 0
x2= x1 - (a - a2
x3= x1 -(2a - a2 - a3

xn =x1 - ((n-1)a - a2- a3 + a4 + … + an
设 x1之后的部分为Ci
x1 = x1 - C1
x2= x1 - C2
x3= x1 - C3

xn =x1 - Cn
我们要求的目标是| x1 | + | x2 | + … +| xn |的最小值
即求| x1 - C1 | + | x1 - C2 | + … +| x1 - Cn |的最小值
∵| x1 - Cn | = | -(Cn - x1) | = | Cn - x1 |
∴ | Cn - x1 | + | x1 - C1 | ≥ |Cn - C1 |(温馨提示由| x | + | y | ≥ | x + y | 推导而来)
前后两两分组,取中位数即可取得最小值。(与货仓选址(邮递员问题)思路类似,看不懂的可以先看货仓选址)。
证明能取得最优解
∵ x1 取的是 Ci(1≤i≤n)中位数。
∴x1 - Ci肯定有一部分大于等于0,一部分小于等于0。
则必存在情况①xi ≥ 0且xi+1≤ 0 (对于第i+1堆糖果而言,都是别人给他或者他不用给别人),②xi ≤ 0且xi+1 ≥ 0 (对于第i+1堆糖果而言,都是他给别人或者他不用给别人)这两种情况先(给/收)左边这家还是先(给/收)右边这家,顺序无影响。所以能将环断开成链从而退化成均分纸牌问题(与均分纸牌思路类似,看不懂的可以先看均分纸牌)。注意点:别人给他,他再给下一家,这种顺序是有影响的。别人要先给他,他才够糖果给别人。
具体方案递归策略:
①xi ≥ 0且xi+1≤ 0 时先递归别人,最后别人再把糖果传递给他。
②xi ≤ 0且xi+1 ≥ 0 时他先把糖果传递给别人,最后再递归计算别人。

代码

#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long LL;
const int N=1000005;
LL s[N],c[N];
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%lld",&s[i]);
        s[i]+=s[i-1];//s[i]表示a[i]的前i项和
    }
    LL avg = s[n]/n;//求a[i]的平均数,因为s[n]是a的前n项和,所以直接除n即可
    for(int i=2;i<=n;i++){//c[1]=0,所以直接从2开始计算即可
        c[i] = (i-1)*avg - (s[i]-s[1]);//求c[i]=(i-1)*avg-(a[2]+a[3]+...+a[i])
    }//参数:起始下标,选择下标,结束下标 
    nth_element(c+1,c+(n+1)/2,c+n+1);//求c数组的中位数,注意:下标从1开始
    LL md = c[(n+1)/2];//将中位数赋值给变量md
    LL res = 0;
    for(int i =1;i<=n;i++){//统计差值,思路与货仓选址类似
        res += abs(c[i]-md);
    }
    cout<<res<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值