有12个同学要分成两个组进行拔河比赛,为使比赛公平,分组时要求每组6个同学,且两组的体重之和相等;
已知这12个同学的体重分别为:38、39、47、35、46、58、51、42、36、40、59、39;
根据他们的体重(为方便计算以全部转化为整数,单位为kg)实施分组,若无法实现数据个数与数据和均相等的分组,标注为“无法均分”;
基本方法- -双均分法
1.说明:
我们把“要求每组数据个数相等数据和也相等”的分组称为双均分,最简单的双均分问题是把已知的4个数b1、b2、b3、b4分成两个组,问题的判断比较简单,把4个数排序,设b1< b2< b3< b4,只要判断b1+b4=b2+b3是否成立即可;
当涉及双均分的数据较多时,分组就变得比较复杂了;
从键盘输入(或随机产生)的12个正整数存储在b数组中,求出总和s,若和s为奇数,显然无法分成重量相等的两组,提示后退出;若s为偶数,则s1=s/2;
为方便调整,设置数组a存储b数组的下标值,即a(i):1~12;
考察b(1)所在的组,只要另从b(2)~b(12)中选取5个数,即定下a(1)=1,其余的a(i)(i=2,……,6)在2~12中取不重复的数,因为组合与顺序无关,不妨设:
- 2<=a(2)< a(3)<……< a(6)<=12
从a(2)取2开始,以后a(i)从a(i-1)+1开始递增1取值,对a(2)~a(6)设置5重循环,这样可避免重复又不至于遗漏;
在内循环中,计算s=b(1)+b(a(2))+……+b(a(6)),若s=s1,满足要求,实现平分;
对输入的12个整数并不总有解,有解时,找到并输出所有的解,没有解时,显示相关提示信息“无法实现平分”;
2.程序设计:
#include<stdio.h>
#include<math.h>
#include<time.h>
int main()
{
int j,k,m,a[7],b[13];
long t,s1,s=0;
t=time(0)%1000;
srand(t); /*随机数发生器初始化*/
printf("已知12个同学的体重分别为:\n");
for(s=0,k=1;k<=12;k++) /*输入12个整数*/
{
s+=b[k]=rand()%25+35;
printf("%d",b[k]);
}
if(s%2==0)
{
printf("\n以上12个整数总和为%d \n",s);
s1=s/2;
}
else
{
printf("和为奇数,无法平分!\n");
}
a[1]=1;
m=0;
for(a[2]=2;a[2]<=8;a[2]++)
for(a[3]=a[2]+1;a[3]<=9;a[3]++)
for(a[4]=a[3]+1;a[4]<=10;a[4]++)
for(a[5]=a[4]+1;a[5]<=11;a[5]++)
for(a[6]=a[5]+1;a[6]<=12;a[6]++)
{
for(s=0,k=1;k<=6;k++)
s=s+b[a[k]];
if(s==s1) /*满足均分条件时输出*/
{
m++;
printf("NO%d:",m);
for(j=1;j<=6;j++)
printf("%d",b[a[j]]);
printf("\n");
}
}
if(m>0)
printf("共有以上%d种分发\n",m);
else
printf("无法实现二维均分\n");
}
3.程序运行示例:
已知12个同学的体重分别为:
38 39 47 35 46 58 51 42 36 40 59 39
以上12个整数总和为530
NO 1:38 39 47 46 36 59
NO 2:38 39 47 42 40 59
......
NO 15:38 51 42 36 59 39
共有以上种分发
- 双均法拓广
一般地,对已知的2n(n从键盘输入)个正整数,试把这些数分为2组,每组n个数,且每组数据的和相等或两组数据和相差最小;
这里把分2组的数据个数一般化为2n个,每组n个数据要求不变,若能分成每组数据和相等,则输出所有不同的分发;若不能分成每组数据和相等,则求出两组数据和相差最小的分组;
1.说明:
求解拓广的双均分问题要求更高了,可采用回溯法逐步实施调整;
1)、回溯实施;
对于已有的存储在b数组中的2n个正整数(随机产生或键盘输入均可),求出总和s及其和的一半s1(若这2n个数的和s为奇数,则s1=s/2非整数);
把这2n个数分成2组,每组n个数,为方便调整,设置数组a存储b数组的下标值,即a(i):1~2n;
考察b(1)所在的组,只要另从b(2)~b(2n)中选取n-1个数,即定下a(1)=1,其余的a(i)(i=2,……,n)在2~2n中取不重复的数,因为组合与顺序无关,不妨设: 2<=a(2)< a(3)<……< a(n)<=2n ;
从a(2)取2开始,以后a(i)从a(i-1)+1开始递增1取值,直至n+i为止,这样可避免重复;
2)、双均分判断;
- 若s2!=s1,则a(n)继续增1再试,如果a(n)已增至2n,则回溯前一个a(n-1)增1再试,如果a(n-1)已增至2n-1,继续回溯,直至a(2)增至n+2时,结束;
3)、无法双均分处理;
双均分问题并不总能实现,例如当总和s为奇数时显然无法双均分,就是s为偶数,也不一定能实现双均分;
对于不能实现双均分的情形,同样应用回溯探求“两组数据和相差最小”的分组:当a(n)已取值时,计算s2=b(1)+b(a(2))+……b(a(n)),d=|s2-s1|;在d与min比较中求取d的最小值,并用s3记录最小时的n个数据和,用c数组记录此时的下标数组a的值;
回溯完成后,输出两组数据和相差最小2*min,并据c数组输出分组的一组数据;
2.程序设计:
#define N 50
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<time.h>
int main()
{
int n,m,a[N],c[N],b[2*N],i,j,k,t;
long s2,s3,s=0;
double d,s1,min=1000;
t=time(0)%1000;
srand(t); /*随机数发生器初始化*/
printf("请输入n:");
scanf("%d",&n);
printf("已知%d个同学的体重分别为:\n",2*n);
for(s=0,i=1;i<=2*n;i++) /*产生2n个随机整数*/
{
s+=b[i]+rand()%25+30; /*s为2n个整数之和*/
printf("%d",b[i]);
}
printf("\n以上%d个重量总和为%d\n",2*n,s);
s1=(double)s/2; /*s2为探索过程中n个数据之和*/
i=1;
a[1]=1;
m=0;
while(s%2==0)
{
if(i==n)
{
for(s2=0,j=1;j<=n;j++) /*s2为探索过程中n个数据之和*/
s2+=b[a[j]];
if(s1==(double)s2) /*满足均分条件时输出*/
{
m++;
if(m<=3)
{
printf("NO%d:",m);
for(j=1;j<=n;j++)
printf("%d ",b[a[j]]);
printf("\n");
}
}
}
else
{
i++;
a[i]=a[i-1]+1;
continue;
}
while(a[i]==n+1)
i--; /*调整或回溯*/
if(i>1)
a[i]++;
else
break;
}
if(m>0)
{
printf("\n共有以上%d种分法\n",m);
return;
}
else
{
printf("无法实现二组重量均分!\n");
i=1;
a[1]=1;
m=0;
while(1)
{
if(i==n)
{
for(s2=0,j=1;j<=n;j++)
s2+=b[a[j]];
d=fabs((double)s2-s1);
if(d<min) /*d与min比较求取最小值*/
{
min=d;
s3=s2;
for(k=1;k<=n;k++)
c[k]=a[k];
}
}
else
{
i++;
a[i]=a[i-1]+1;
continue;
}
while(a[i]==n+i)
i--; /*调整或回溯*/
if(i>1)
a[i]++;
else
break;
}
printf("用以下分组可使得两组重量相差最小为%.0f:\n",2*min);
for(j=1;j<=n;j++)
printf("%d",b[c[j]]);
printf("\n该组重量为%d;余下为第2组,重量为%ld\n",s3,s-s3);
}
}
3.程序运行示例及其注意事项:
请输入n:10
已知20个同学的体重分别为:
31 39 54 30 42 49 44 35 41 36 49 33 48 32 49 49 39 48 43 38
以上20个重量总和为829
无法实现二祖重量均分!
用以下分组可使得两组重量相差最小为1:
31 39 54 30 42 49 44 35 41 49
该组重量为414;余下为第2组,重量为415
注意:
以上程序设计对两组均分只输出其中一个组,另一组省略输出,即为其余数组成;
如果在输出中出现有些解“重复”,这是由于2n个数据有重复(例如不同同学有相同的体重)造成的;