第 5 章 精巧求解剖析 精巧求解,包括高精度计算,是最具吸引力的亮点,也是让人望而却步的难点。 本章从互积和与嵌套根式和的巧算、同码数的整除及同码数求和规律的探索,到建模 统计、分类统计、游戏中的素数概率求解,突出一个“巧”字。同时,从探索最小0-1串积到 指定多码串积与尾数前移问题等,突显一个“精”字。 探讨并拓展著名的梅齐里亚克砝码问题与伯努利装错信封的排列问题,是“巧”与“精” 结合的典范。飘逸于数学殿堂的两个“幽灵”e和π,凝聚着数学的精华,彰显出编程的卓越。 5.1 和与积巧算 本节探讨简单的互积和与嵌套根式和,不存在难点,但求解技巧颇具启发性。 5.1.1 互积和 探求已知整数组的互积和是涉及和与积的常规题,有较强的技巧性,常常作为数学奥 赛的培训题或测试题。 有9个整数:-3,-2,-1,0,1,2,4,8,16,对这9个整数求任意2个数之积,任意3 个数之积,……,直至所有9个数之积。把所有这些积之和称为这些整数的互积和。 【问题】 试求这9个整数的互积和m 。 【分类求解】 互积和分类是简化求解的关键。 可忽略0,因凡含有0的积结果均为0,对最后结果没有影响。 注意到所有正数和为31,分以下5类情形考虑。 (1)所有正数互积和,设为s1(不含单个正数)。 (2)所有负数互积和:s2=2+3+6-6=5。 (3)含1个负数的互积和:s3=(-1-2-3)(s1+31)=-6s1-6×31。 (4)含2个负数的互积和:s4=11×(s1+31)=11s1+11×31。 (5)含3个负数的互积和:s5=-6×(s1+31)=-6s1-6×31。 以上5项求和,巧妙消去其中尚未算出的s1,得到互积和m 。 m =s1 +s2 +s3 +s4 +s5 =5-31=-26 【妙思巧解】 巧妙构造多项式化互积为多项式积。 记8个非零整数为a1,a2,…,a8,构造多项式 15 4 f(x)=(x +a1)(x +a2)…(x +a8)=x8 +p1x7 +p2x6 + … +p7x +p8 (5-1-1) 其中,p1 为8个数之和25;p2 为任意2个数积之和;p3 为任意3个数积之和;……;p8 为 所有8个数之积。显然,所求互积和m =p2+p3…+p8。 令x=1,由式(5-1-1)左边知f(1)=0(因有一整数为-1);同时,由式(5-1-1)右边 有f(1)=1+p1+p2+…+p8=1+25+m ,因而所求结果为m =p2 +p3 +…+p8 = -26。给 出的整数中有-1,即得和f(1)=0,这是减少计算量的关键所在。 即使给出的8个整数中没有-1,计算式(5-1-1)左边的8个数之积也远比计算“互 积”简单。 【概括】 以上两个求解都颇具启发性。如果按“互积”的定义,具体实施每2个数求 积,每3个数求积,……,这一思路并不可行。而分类的思想和建多项式的思想,就是化解 “互积”这一难点的巧妙突破点。 如果对于指定的一般n 个整数,如何求取其互积和? 【编程拓展】 拓展至一般情形,探求n 个整数的互积和。 对给出的n 个整数,计算任意2个数之积,任意3个数之积,……,直到所有n 个数之 积。定义所有这些积之和为这n 个整数的互积和m 。 从键盘输入n 个整数,探求并输出这n 个整数的互积和m 。 (1)设计要点。 按上述巧妙构造多项式,化互积为多项式积。 设置存储n 个整数的a 数组,从键盘输入的n 个整数存储在a[1],a[2],…,a[n]。 同时构造关于这n 个整数的n 次多项式 y(x)=(x +a1)(x +a2)…(x +an)=xn +p1xn-1 +p2xn-2 + … +pn-1x +pn (5-1-2) 其中,p1 为n 数之和;p2 为任意2个数积之和;……;pn-1为任意n-1个数积之和;pn 为 所有n 个数之积。显然,m =p2+…+pn 。 令x=1,按式(5-1-2)左边通过循环求积,可简单得出y(1);按式(5-1-2)右边有 y(1)=1+p1+p2+…+pn-1+pn ;同时在循环中求出n 个整数之和p1,显然所求互积 和为 m =y(1)-p1 -1 (5-1-3) 循环结束,即按式(5-1-3)输出所求n 个整数的互积和m 。 (2)程序设计。 //求n 个整数的互积和m #include void main() { long k,m,n,s,y,a[100]; printf(" 请确定整数的个数n:");scanf("%ld",&n); s=0;y=1; 1 55 for(k=1;k<=n;k++) //逐个输入n 个整数 { printf(" 请输入第%ld 个整数:",k); scanf("%ld",&a[k]); s+=a[k]; y*=(1+a[k]); //计算整数和s 及f(1) }m =y-1-s; //计算互积和m printf(" 输入的%ld 个整数为%ld",n,a[1]); for(k=2;k<=n;k++) //集中输出n 个整数 printf(", %ld",a[k]); printf("\n 以上%ld 个整数的互积和m=%ld。\n",n,m); //输出结果 } (3)程序运行示例与说明。 请确定整数的个数n:8 输入的8 个整数为-5, -3, -2, 3, 5, 8, 10, 13 以上8 个整数的互积和m=-266142。 运行程序时,输入n 个整数的先后顺序对互积和结果没有影响。 以上程序把求互积(若干积)转化为求一个积,是简化复杂运算的技巧所在。 同时,程序设置在输入循环中输入与计算(和与积)同步进行,输入循环结束,计算也 随之完成。 如果运行程序,输入9个整数-3,-2,-1,0,1,2,4,8,16,即可得到互积和为-26, 其中的整数-1是一个简化因子。 即使没有-1这一简化整数,以上程序实现把“互积”难点转化为在循环中求一个积, 也非常精巧。 5.1.2 嵌套根式和 试求以下两个嵌套根式和 s1 = 1+ 2+ 3+ … + n (5-1-4) s2 = 5+ 3+ 5+ 3+ … + 5+ 3 (式中有n 个5与n 个3) (5-1-5) 输入正整数n,输出根式和s1 与s2(精确到小数点后8位)。 1. 设计要点 对于给定的正整数n,在s1 中涉及n 层开方,而s2 涉及2n 层开方,新颖少见。 根式和式(5-1-4)与式(5-1-5)存在根号嵌套,处理根号嵌套这一难点是设计的关键。 按通常由起点1到终点n 设置循环,不便于处理根号嵌套这一难点。但若反过来逆 向设置循环,解决根号嵌套就顺理成章。 (1)实现s1 求和。 设置a(n-1~1)循环枚举整数a,循环外赋初值“s1=n;”,循环内求累加和。 15 6 s1=a+sqrt(s1); 赋值表达式中的sqrt(s1),即可实现s1 的根号嵌套。 当a=n-1时,“s1=(n-1)+sqrt(n);”实现最内1层根号。 当a=n-2时,“s1=(n-2)+sqrt((n-1)+sqrt(n));”实现最内2层根号。 …… 当a=1时,s1 实现式(5-1-4)除最外层之外的其他所有n-1层根号。 最后一层根号在输出结果时完成。 (2)实现s2 求和。 同样设置a(n-1~1)循环,循环外赋初值“s2=5+sqrt(3);”,循环n-1次。 s2=5+sqrt(3+sqrt(s2)); 赋值表达式中的sqrt(s2)即可实现s2 的根号嵌套。 当a=n-1时,“s2=5+sqrt(3+sqrt(5+sqrt(3)));”实现最内3层根号。 当a=n-2时,“s2=5+sqrt(3+sqrt(5+sqrt(3+sqrt(5+sqrt(3)))));”实现最内 5层根号。 …… 当a=1时,s2 实现式(5-1-5)除最外层之外的其他所有2n-1层根号。 最后一层根号在输出结果时完成。 2. 程序设计 //探求两个根式和 #include #include void main() { double a,n,s1,s2; printf(" 请输入正整数n(n>1): "); scanf("%lf",&n); s1=n;s2=5+sqrt(3); for(a=n-1;a>=1;a--) //枚举n-1~1 的整数a { s1=a+sqrt(s1); s2=5+sqrt(3+sqrt(s2)); } printf(" s1=%.8f\n",sqrt(s1)); //最后的和s1 与s2 需开平方 printf(" s2=%.8f\n",sqrt(s2)); } 3. 程序运行示例与变通 请输入正整数n(n>1): 100 s1=1.75793276 s2=2.71870969 在所设置的a 循环中,应用“s1=a+sqrt(s1);”及“s2=5+sqrt(3+sqrt(s2));”是实 现根号嵌套的技巧所在。 变通:容易修改以上程序,探求区间[c,d]中的根式和 s1= c+ c+1) + c+2) + … + 516) d(((- s2= dccc+ c+1) + + c+2) + … + + 5-1-7) 输入区间上下限正整数c( ,c void main() { int a,c,p,n; printf(" 请输入整数p: "); scanf("%d",&p); if(p%2==0 || p%10==5) { printf(" 不存在同码数整除%d。",p); return; } n=1; c=1; //确定初始值n,c while(c!=0) { a=c*10+1; c=a%p; n++;} //实施除乘竖式计算模拟 printf(" 至少需%d 个1 的整数才能被%d 整除。\n",n,p); } (3)程序运行示例与说明。 请输入整数p: 2019 至少需672 个1 的整数才能被2019 整除。 输出结果是至少需672个1的整数才能被2019整除,靠人工实施竖式除法,想得出 这一结果还是比较繁复的。 这还不算,试试“至少需多少个1的整数才能被2017 整除”,就更能切身体会到人工 计算与程序计算的效益差别。 2.同码数求和 5.2 本节探索n个同码数求和,包括同码整数求和与同码小数求和。设和式 s(d,= n 个d)(- n)d+dd +ddd + …+dd…d(522) f(d,n)=d+0.ddd + …+0.小数点后 n 个d) (523) 0.dd +0.dd…d( - 式(5-2-2)为 n 个同数码 d 整数之和,和式中第 k 项有 k 个数字d(d=1,2,…,9)。 例如,s(3,5)=3+33+333+3333+33333 。 式(5-2-3)为 n 项同数码 d 小数之和,其中第 k 项小数点后有连续 k 个数字d(d=1, 2,…,9)。 7+0.777+0.例如,f(7,4)=0.77+0.7777 。 【问题1】3, 试求同码整数和s(30 )。 【求解】引入中间量s(9,30)用以简化求和。 引入中间量s(9,30)是有趣的,因为s(9,30)与s(3,30)直接相关,而s(9,30)可以直 接算出。事实上,由 s(3,30)=s(9,30)/9×3=s(9,30)/3 (5-2-4) 而s(9,30)=9+99+…+99…9 =(10-1)+(100-1)+…+(1030-1) =11…10-30=11…1080(最后数中28 个1) s(3,30)=11…1080/3 (被除数中28 个1) 注意到1111/3=370,余数1;1080/3=360,因而得 (s(3,=....360 - 30)370.. …370525) ........ 9个370 有趣的是,以上s(3,30)的结果式(5-2-5)中出现9个370 重复节,耐人寻味。 【问题2】试求同码小数和f(6,30 )。 【求解】引入中间量f(9,30)用以简化求和。 同样引入中间量f(9,30), 因为f(9,30)与f(6,30)直接相关,而且f(9,30)可以直 接算出。由 f(6,30)=f(9,30)/9×6=f(9,30)×2/3 (5-2-6) 而 f(9,=0.99+ …+0.30-0.后项小数共连续30 个1) 30)9+0.99…9=11…1( 则 f(6,=(111…1)×2/3=(222…2)/3 30)30-0.60-0. 因而 f(6,=59.19+2.其中后项小数有连续29 个7) 30)77…78/3=77…78/3( 注意到2777/3=925,余数为2;而2778/3=926,因而 得 159 16 0 f(6,30)=19.9..2..5..…....9..2..5 9个925 926 同样耐人寻味的是,f(6,30)的结果中出现9个重复节925。 【编程拓展】 探求同码整数和s(d,n)与同码小数和f(d,n)。 输入整数d(1≤d≤9),及整数n(1 void main() { int d,i,j,n,s[5000],f[5000]; printf(" 请输入整数d,n: "); scanf("%d,%d",&d,&n); for(j=0;j<=n;j++) s[j]=f[j]=0; for(i=1;i<=n;i++) s[i]=f[i]=(n-i+1)*d; //第i 位共n+1-i 个d 之和 1 61 for(j=1;j<=n-1;j++) //加完n 个整数后统一进位 { s[j+1]=s[j+1]+s[j]/10;s[j]=s[j]%10;} printf(" s(%d,%d)=",d,n); for(j=n;j>=1;j--) //从高位开始逐位输出和s(d,n) printf("%d",s[j]); for(j=n;j>=1;j--) //加完n 个小数后统一进位 { f[j-1]=f[j-1]+f[j]/10;f[j]=f[j]%10;} printf("\n f(%d,%d)=%d.",d,n,f[0]); for(j=1;j<=n;j++) //从小数点后第一位开始逐位输出和 printf("%d",f[j]); printf(" \n"); } 3. 程序运行示例与说明 请输入整数d,n: 7,30 s(7,30)=864197530864197530864197530840 f(7,30)=23.246913580246913580246913580247 从上述整数求和结果看,864197530这一“重复节”在和中重复3次,只有最后3位 840不属于重复节。 从上述小数求和结果看,小数点后除了尾部3位247之外,其余是246913580这一 “重复节”,重复3次。 同码数求和结果中的“重复节”与循环小数的“循环节”有类似之处,不同的是重复节 往往在前面,而循环节通常在后面。 5.3 统计的智慧 本节探讨巧妙建模、三角网格与交通方格网3个有代表性的统计案例,其统计思路与 方法颇为新颖。 5.3.1 巧妙建模 本案例探求一个线性不定方程的解有多少组,应用建模简化了统计过程。 【问题】 对于不定方程x+y+z=15,试统计: (1)x,y,z 为非负整数解的组数; (2)x,y,z 为正整数解的组数; (3)x,y,z 满足x≥-3,y≥2,z≥5的整数解的组数。 【建模巧解】 拟建立投球统计模型,转化为组合计算。 (1)把问题转化为15个相同的小球投放到3个分别标有x,y,z 标签的盒子中的不 同的投放种数。然后,把3个盒子“抽象”为并排设置的2块隔板,这2块隔板划分的3个 区域相当于3个盒子,即x,y,z 变量。于是把15个小球与这2块隔板进行排列,每种不 16 2 同的排列对应一种投球结果,即对应不定方程x+y+z=15的一组解。排列种数等于 15+2个位置中挑选出2块隔板位置的组合数C(15+2,2)。因而x,y,z 为非负整数解 的组数为C(17,2)=136。 (2)若x,y,z 为正整数,相当于每个盒子至少要投放一个小球,不妨每个盒子先放一 个小球。然后15-3=12个小球与2块隔板进行排列,共有组数即为组合数C(12+2, 2)。因而x,y,z 为正整数解的组数为C(14,2)=91。 (3)若x,y,z 满足x≥-3,y≥2,z≥5,令a=x+3,b=y-2,c=z-5,则由x+ y+z=15得a+b+c=11(a,b,c≥0),由上可知这一方程的非负整数解组数为组合数 C(11+2,2)。因而x,y,z 满足x≥-3,y≥2,z≥5的整数解的组数为C(13,2)=78。 【编程拓展】 对于给定的正整数和s,对于3个变量x,y,z 的不定方程x+y+z=s,试统计x,y, z 取自区间[c,d](0≤c #include void main() { int c,d,n,s,x,y,z; printf(" 请确定各变量起点c 与终点d:"); scanf("%d,%d",&c,&d); printf(" 请确定和s(3c