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

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

输出样例

11

        (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

int main()

{

   int n,x;

   scanf("%d%d",&n,&x);

   int a[200001];

   int i;

   for (i=0;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

   {

      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)

输出

一行:即所有小朋友手里糖果数均相等时的最少传递次数。

输入样例

9 8 17 6

输出样例

      (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

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。

输出

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

输入样例

输出样例

        (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

#include

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;

}

相关