算法竞赛入门经典--训练指南之分金币

本文介绍了一种算法,用于解决圆桌旁的人通过最少金币转移使每人持有相同数量金币的问题。核心思路是找到金币分配的中位数,以此作为均衡点,从而计算出最小金币转移量。

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

圆桌旁坐着n个人,每人有一定数量的金币,金币总能被n整除。每个人可以给他左右相邻的人一些金币,最终使得每个人的金币数目相等。你的任务是求出被转手的金币数量的最小值。比如,n=4,且4个人的金币数量分别为1,2,5,4时,只需转移4枚金币(第3个人给第二个人两枚金币,第二个人和第四个人分别给第一个人1枚金币)即可实现每人手中的金币数目相等。

输入格式:

#include<stdio.h>
#include<math.h>

void sort(__int64 *a,__int64 I){
	int val,i,j;
	for(i=0;i<I-1;i++)
	for(j=i+1;j<I;j++){
		if(a[i]<a[j]){
			val=a[i];
			a[i]=a[j];
			a[j]=val;
		}
	}
}

int main(){
	__int64 n;
	while(scanf("%I64d\n",&n) == 1){
		__int64 i,a[n],sum=0;
		for(i=n;i>0;i--)//输入 
	{
		scanf("%I64d\n",&a[i]);
		sum+=a[i];
	}
	__int64 avg=sum/n,c[n];
	c[0]=0;
	for(i=0;i<n;i++)
	{
		c[i]=c[i-1]+a[i]-avg;
	}
	sort(c,n);
	__int64 medium=c[n/2],ans=0;
	for(i=0;i<n;i++){
		ans+=abs(medium-c[i]);
	}
	printf("%I64d\n",ans);
	}
	return 0;
}

输入包含多组数据。每组数据第一行为整数n(n<=1000000),以下n行每行为一个整数,按逆时针顺序给出每个人拥有的金币数。输入结束标志为文件结束符(EOF)


输出格式:

对于每组数据,输出被转手金币数量的最小值。输入保证这个值在六十四位无符号整数范围内。


分析:

这道题目看起来很复杂,让我们慢慢分析。首先,最终每个人的金币数量可以计算出来,它等于金币总数除以人数n。接下来我们用avg来表示每人最终拥有的金币数。

假设有四个人,按顺序编号为1,2,3,4.假设1号给2号3枚金币,然后2号又给1号5枚金币。这等价于2号给1号2枚金币,而1号什么也没给2号。这样可以设置x2表示2号给了1号多少个金币。如果x2<0,说明实际上是1号给了2号-x2枚金币。x1,x3和x4的含义类似。注意,由于是环形,x1指的是1号给4号多少金币。

现在假设编号为i的人初始有ai枚金币。对于1号来说,他给了4号x1枚金币,还剩ai-x1枚金币;但因为2号给了他x2枚金币,所以最后还剩ai-x1+x2枚金币。根据题设,该金币数等于avg。

同理,对于第二个人,有a2-x2+x3=avg.最终,我们可以得到n个方程,一共有n个变量,是不是可以直接解方程组了呢?很可惜,还不行。因为从前n-1个方程可以推导出最后一个方程。所以,实际上只有n-1个方程是有用的。

尽管无法直接解出答案,我们还是可以尝试着用x1表示出其他的xi,则本题就变成了单变量的极值问题。

对于第一个人,a1-x1+x2=avg->x2=avg-a1+x1=x1-c1

对于第二个人,a2-x2+x3=avg->x3=avg-a2+x2=avg-a2+age-a1+x1=x1-c2

......

我们希望所有xi的绝对值之和尽量小,即|x1|+|x1-c1|+|x1-c2|+...+|x1-cn-1|要最小。注意到|x1-ci|的几何意义是数轴上点x1到ci的距离。所以问题变成了:给定数轴上的n个点,找出一个到他们的距离之和尽量小的点。

该点便是中位数

证明:

给定数轴上的n个点,在数轴的所有点中,中位数离所有顶点的距离之和最小。凡是能转化为这个模型的题目都可以用中位数求解,并不只适用于本题。

任意找一个点,假设它左边有四个输入点,右边有两个输入点。把它往左移动一点,不要移的太多,以免碰到输入点,假设移动了d单位距离,则该点左边4个点到它的距离各减少了d,右边的两个点到它的距离各增加了d。但总的来说,距离之和减少了2d。

如果该点左边有两个点,右边有四个点,道理类似,不过应该向右移动。换句话说,只要该点左右的输入点不一样多,就不是最优解。什么情况下左右的输入点一样多呢?如果输入点一共有奇数个,则该点必须与中间的那个点重合(中位数);如果有偶数个,则该点可以位于最中间的两个点之间的任意位置(还是中位数)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值