C语言程序设计100例之(44):分糖果
例44 分糖果
问题描述
十个小孩围坐一圈分糖果,开始时,老师随机分给每位小孩若干糖果。为了公平,现进行调整,调整规则:所有小孩同时把自己糖果的一半分给左边的小孩,调整分一半时如果哪位小孩的糖果数为一个奇数,向老师补要1块(设老师手中的糖果足以满足这些要求)。问经过多少次调整,大家的糖果数都一样?每人多少块?
输入格式
10个正整数,表示10个小孩初始糖果数。
输出格式
调整次数和每个小孩的糖果数。
输入样例
15 23 18 6 14 22 9 13 8 10
输出样例
After 15 times,all children have same number of sugar.
Every child has 20 sugars.
(1)编程思路。
这是一道典型的模拟题。我们用逐步求精的方法来分析这个问题的解决方法。
1)先写出程序的总体框架如下:
输入10个小孩的初始糖果数①;
While(10个小孩的糖果数不全相等② )
{
要进行一次调整过程,调整次数加1;
糖的块数为奇数的小孩向老师补要一块③ ;
所有小孩同时把自己糖果的一半分给左边的小孩④ ;
}
输出结果信息
在这个总体框架中需要解决的4个问题我们用下划线标记出来了。
2)设定义一个整型数组a来保存10个小孩的糖果数,问题①就是需要输入10个数组元素的初始值,程序代码为:
for (i=0;i<10;i++)
cin>>a[i];
3)问题②需要判断10个小孩的糖果数是否相等,显然是一个操作序列,其判断结果是while循环的条件,因此将问题②抽象成一个函数AllEqual,该函数用来判断数组中所有元素的值是否都相等,如果都相等则返回1,否则返回0。其函数原型为:
int AllEqual(int x[]);
而为判断一个数组中所有元素的值是否全相等,最简单的办法为将数组中的第2个数至最后一个数与第1个数相比较,只要它们中有一个不相等,就返回0(不全相等),如果比较完后,没有返回0,则它们全相等,返回1。
程序描述为:
int AllEqual(int x[10])
{
int i;
for (i=1;i<10;i++)
if (x[i]!=x[0]) return 0;
return 1;
}
4)问题③可以用一个循环程序解决,对数组中的每个元素判断其奇偶性,如果为奇数,则将该元素值加1。程序代码为:
for (i=0;i<10;i++)
if (a[i]%2!=0) a[i]++;
我们将这个循环操作写成一个函数。函数定义如下:
void supply(int x[10])
{
int i;
for (i=0;i<10;i++)
if (x[i]%2!=0) x[i]++;
}
主函数中的调用语句为:
supply(a);
5)完成一次调整过程,所有小孩需要同时把自己糖果的一半分给左边的小孩,如下图1所示。
图1 一次调整过程示例图
由图看出,
当i=1~9时,有 a(i)= (a(i)+a(i-1))/2
i=0时, a(0)=(a(0)+a(9))/2
因此,很容易地想到可以写成如下的代码段:
a[0]=a[0]/2+a[9]/2;
for (i=1;i<9;i++)
a[i]=a[i]/2+a[i-1]/2;
这样写是错误的,为什么呢?因为先修改a[1],当计算a[2]时,用到的a[1]已经被修改了。
应该写成:
temp=a[9];
for (i=9;i>0;i--)
a[i]=a[i]/2+a[i-1]/2;
a[0]=a[0]/2+temp/2;
问题④我们同样将它写成一个函数,函数定义为:
void exchange(int x[10])
{
int temp,i;
temp=x[9];
for (i=9;i>0;i--)
x[i]=x[i]/2+x[i-1]/2;
x[0]=x[0]/2+temp/2;
}
至此,可以写出完整的源程序。
(2)源程序。
#include <stdio.h>
int AllEqual(int x[]);
void exchange(int x[]);
void supply(int x[]);
int main()
{
int a[10],i,count=0;
for (i=0;i<10;i++)
scanf("%d",&a[i]);
while (AllEqual(a)!=1)
{
count++;
supply(a);
exchange(a);
}
printf("After %d times,all children have same number of sugar.\n",count);
printf("Every child has %d sugars.\n",a[0]);
return 0;
}
int AllEqual(int x[10])
{
int i;
for (i=1;i<10;i++)
if (x[i]!=x[0]) return 0;
return 1;
}
void exchange(int x[10])
{
int temp,i;
temp=x[9];
for (i=9;i>0;i--)
x[i]=x[i]/2+x[i-1]/2;
x[0]=x[0]/2+temp/2;
}
void supply(int x[10])
{
int i;
for (i=0;i<10;i++)
if (x[i]%2!=0) x[i]++;
}
习题44
44-1 小A的糖果
本题选自洛谷题库 (https://www.luogu.org/problem/P3817)
题目描述
小A有N个糖果盒,第i个盒中有a[i]颗糖果。
小A每次可以从其中一盒糖果中吃掉一颗,他想知道,要让任意两个相邻的盒子中加起来都只有x颗或以下的糖果,至少得吃掉几颗糖。
输入格式
输入包括多组测试用例。每组用例包括两行输入数据。
第一行输入N和x。
第二行N个整数,为a[i]。
输出格式
每组测试用例输出一行,该行为至少要吃掉的糖果数量。
输入样例
3 3
2 2 2
6 1
1 6 1 2 0 4
5 9
3 1 4 1 5
输出样例
1
11
0
(1)编程思路。
要让任意两个相邻的盒子中加起来都只有x颗或以下的糖果,可以把这几个糖果盒成对来讨论。先从第1个和第2个糖果盒开始;如果第1个糖果盒的数量就超过x了,当然至少要把它吃到剩下x个;然后如果相邻两个盒子中每个盒子糖果数都没有超过x,但加起来超过x了,怎么处理呢? 首先第1个糖果盒是只有一个分组的(和第2个), 而第2个糖果盒却有两个分组(和第1个或和第3个); 所以如果吃掉第一个糖果盒里的,只会减少一个分组的量,而如果吃掉第2个糖果盒里的,可以减少2个分组的量。所以要尽量吃掉第2个盒里的糖果。处理好第1个分组(即第1个和第2个糖果盒的分组)后,来看第2个分组(即第2个和第3个糖果盒的分组),因为第1个分组已经被处理好了,所以以后可以不管它,然后问题又变成了前一个问题。以此类推就可以求得结果。
(2)源程序。
#include <stdio.h>
int main()
{
int n,x;
scanf("%d%d",&n,&x);
int a[200001];
int i;
for (i=0;i<n;i++)
scanf("%d",&a[i]);
long long ans=0;
if (a[0]>x)
{
ans+=1ll*(a[0]-x);
a[0]=x;
}
for (i=1;i<n;i++)
{
if (a[i-1]+a[i]>x)
{
ans+=1ll*(a[i-1]+a[i]-x);
a[i]=x-a[i-1];
}
}
printf("%lld\n",ans);
return 0;
}
44-2 均分糖果
题目描述
有N个小朋友做成一排,编号分别为 1,2,…,N,每个小朋友有 ai个糖果。糖果总数为N的倍数。为了公平,现在需要进行调整,使得每位小朋友的糖果数均相等。调整方法是:编号为1的小朋友只能将糖果传递给编号为2的小朋友;在编号为N的小朋友,只能把糖果传递给编号为N-1的小朋友;其他小朋友手上的糖果,可以传递到相邻左边或右边的小朋友手上。
现在要求找出一种调整方法,用最少的传递次数使每位小朋友手上的糖果数都一样。
例如,N=4,4个小朋友手里的糖果数分别为:9 8 17 6,传递3次可达到目的:
3号小朋友将手里的糖果给4颗给4号小朋友(9,8,13,10),3号小朋友再将手里的糖果给3颗给2号小朋友(9,11,10,10),2号小朋友将手里的糖果给1颗给1号小朋友(10,10,10,100)。每个小朋友手里均有10颗糖果。
输入
两行
第一行为:N(N个小朋友, 1≤N≤100)
第二行为:A1,A2, … ,An(每位小朋友手里的糖果数,1≤Ai ≤10000)
输出
一行:即所有小朋友手里糖果数均相等时的最少传递次数。
输入样例
4
9 8 17 6
输出样例
3
(1)编程思路。
设n个小朋友的平均糖果数为aver。
第1位小朋友手里的糖果数与平均数的差值(a[1]-aver)只能与第2个小朋友传递,若多余平均值(即a[1]-aver>0),则将多余的糖果(a[1]-aver)传递给第2个小朋友;若小于平均值(即a[1]-aver<0),则需要第2个小朋友传递aver-a[1]颗糖果给他。传递后,第2位小朋友的糖果数为 a[2]+a[1]-aver,传递次数均加1。若a[1]正好等于aver,则不用传递,传递次数不变。
第一个小朋友手里的糖果数达到平均值后,可以不用管第1个小朋友了,因为他已经达到目标,再参与传递只会造成浪费。这样,第2个小朋友相当是第1个小朋友了,如此类推,直到剩下最后一个小朋友。
在处理过程中,某个小朋友手里的糖果数变成负数也没关系,其后的小朋友一定会补给他的。例如,有3个小朋友,手里的糖果数分别为:1 1 7,从左到右处理过程如下:
(1,1,7)→(3,-1,7)→(3,3,3)。具体的传递过程可以为:3号小朋友将手里的糖果给4颗给2号小朋友(1,5,3),2号小朋友再将手里的糖果给2颗给1号小朋友(3,3,3)。每个小朋友手里均有3颗糖果。由于题目只需求出最少的传递次数,并不要求给出具体的传递过程,所以从左到右处理,即使中间状态出现负数,不会对结果造成影响。
(2)源程序。
#include <stdio.h>
int main()
{
int n;
scanf("%d",&n);
int a[105];
int i,sum=0;
for (i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum+=a[i];
}
int aver=sum/n;
int ans=0;
for (i=1;i<=n;i++)
if ((a[i]-aver)!=0)
{
a[i+1]+=a[i]-aver;
ans++;
}
printf("%d",ans);
return 0;
}
44-3 糖果传递
题目描述
有n个小朋友坐成一圈,每人有ai个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为1。
输入
小朋友个数 n,下面n行ai。
输出
求使所有人获得均等糖果的最小代价。
输入样例
4
1
2
5
4
输出样例
4
(1)编程思路。
设编号为i的小朋友初始有a[i]颗糖,每个小朋友的糖果数量相同,即为平均数ave。
X[i]表示第i个小朋友给第i-1个小朋友x[i]颗糖,若x[i]<0,表示第i-1个小朋友给了第i个小朋友x[i]颗糖,特别的,x[1]表示1号小朋友给n号小朋友x[1]颗糖。
因此,最后的答案就是ans=|x[1]| + |x[2]| + |x[3]| + …+ |x[n]|。
对于第1个小朋友,他给了第n个小朋友x[1]颗糖,还剩a[1]-x[1]颗糖;但因为第2个小朋友给了他x[2]颗糖,所以最后还剩a[1]-x[1]+x[2]颗糖。根据题意,最后的糖果数量等于ave,由此可得:a[1]-x[1]+x[2]=ave。
同理,对于第2个小朋友,有a[2]-x[2]+x[3]=ave
……
对于第n个小朋友,有a[n]-x[n]+x[1]=ave。
最终,可以得到n个方程,一共有n个变量,但是因为从前n-1个方程可以推导出最后一个方程,所以实际上只有n-1个方程是有用的。
可以用x[1]表示出其他的x[i]。
对于第1个小朋友,a[1]-x[1]+x[2]=ave -> x[2]=ave-a[1]+x[1]= x[1]-S[1] (设s[1]=a[1]-ave,下面类似)
对于第2个小朋友,a[2]-x[2]+x[3]=ave ->x[3]=ave-a[2]+x[2]=2ave-a[1]-a[2]+x[1]=x[1]-s[2]
(s[2]=a[1]+a[2]-2ave=s[1]+a[2]-ave)
对于第3个小朋友,a[3]-x[3]+x[4]=ave -> x[4]=ave-a[3]+x[3]=3ave-a[1]-a[2]-a[3]+x[1]
=x[1]-s[3] (s[3]=a[1]+a[2]+a[3]-3ave=s[2]+a[3]-ave)
……
要是ans的值尽可能小,就要x[i]的绝对值之和尽量小,即
|x[1]| + |x[1]-s[1]| + |x[1]-s[2]| + …+ |x[1]-s[n-1]|要尽量小。
|x[1]-s[i]|的几何意义是数轴上的点X[1]到s[i]的距离,所以问题变成了:给定数轴上的n个点,找出一个到它们的距离之和尽量小的点,而这个点就是这些数中的中位数。即x[1]取中位数即可。
(2)源程序。
#include <stdio.h>
#include <algorithm>
using namespace std;
long long a[1000001],s[1000001];
int main()
{
int n;
scanf("%d",&n);
int i;
long long sum=0;
for (i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum+=a[i];
}
long long aver=sum/n;
s[1] = a[1]-aver;
for (i=2;i<=n;i++)
s[i] = a[i]+s[i-1]-aver;
sort(s+1,s+n+1);
long long ans=0;
for (i=1;i<=n/2;i++)
ans+= s[n+1-i]-s[i];
printf("%lld",ans);
return 0;
}