题目
在海军节的开幕式上,有 A,B,C三艘军舰要同时开始鸣放礼炮各 21 响。已知 A 舰每隔 5 秒放一次,B 舰每隔 6 秒放一次,C 舰每隔 7 秒放一次。假设各炮手对时间的掌握非常准确,请编程计算观众总共可以听到几次礼炮声。
答案:54
代码
#include <stdio.h>
int book[200]={0};
int main()
{
// 注意,假设有0秒的概念
// 换言之,0秒时,A,B,C各自同时发射了礼炮
int a=0, b=0, c=0;
for(int i=1; i<=21; i++)
{
book[a]=1;
a+=5;
book[b]=1;
b+=6;
book[c]=1;
c+=7;
}
int ans=0;
for(int i=0; i<=140; i++)
if(book[i]==1)
ans++;
printf("%d", ans);
return 0;
}
代码分析
注意,假设有0秒的概念。也就是说,在第0秒时,ABC同时发射了礼炮。那么,我们关注C,因为C肯定是最晚才放完礼炮的,这点毋庸置疑。C在第7秒时发射第2枚礼炮,在第14秒时发射第3枚礼炮,在第21秒时发射第4枚礼炮。。。类推,在 第140秒时 发射第 21 枚礼炮。
book数组用来标记时刻的,所以它的每个下表都代表一个时刻,所以它至少是 0~140 那么大的范围。例如:book[i]=1,表示第i秒,有人放礼炮了。可能是一个或两个或3个,反正就是有人放礼炮了。
a表示A放礼炮的时刻,b表示B放礼炮的时刻,c表示C放礼炮的时刻。for循环里面,推导出各个人放礼炮的时刻,然后在book[]数组的对应位置标记为1。如果两个人同时放礼炮,那么 book数组的某个下标会被重复两次赋值为1。
所以最后我们只需要统计 [0, 140] 这个区间的1的个数,就知道了观众听了几次炮响。
错误代码
#include <stdio.h>
int book[160]= {0};
int main()
{
for(int i=0; i<=140; i++)
{
if(i%5==0)
book[i]=1;
if(i%6==0)
book[i]=1;
if(i%7==0)
book[i]=1;
}
int ans=0;
for(int i=0; i<=140; i++)
if(book[i]==1)
ans++;
printf("%d", ans);
return 0;
}
错误代码分析
上面代码输出为 61,跟正确答案 54 相比 多了。在看完正确的代码的分析,发现错误代码的思路似乎也没问题,为什么就是答案偏大呢?
问题出在了A, B,C结束的时间不一样,C是在第140秒放完最后一炮,B是在第120秒,A是在第100秒。而上面的代码,默认A, B也是在140秒内(不一定是第140秒整)放完。
数学解法
在第0秒时,ABC同时发射了第一枚礼炮。那么,他们分别剩下20枚。那么不难想到,
答案 = 1 + ( 20 × 3 - 重叠的炮响次数 t )
重叠的炮响跟公倍数有关,由于0比较特殊,所以就单独分出来了。
重叠的炮响的次数 t = 5,6的公倍数的个数 + 5,7的公倍数的个数 + 6,7的公倍数的个数 - 5,6,7的公倍数的个数
5,6的公倍数的个数:
5,6的最小公倍数是30,由于A更早发射完(C是在第140秒放完最后一炮,B是在第120秒,A是在第100秒),所以公倍数的个数取决于更早发射完那个人。所以,5,6的公倍数的个数 = 100 / 30 = 3。
5,7的公倍数的个数:100 / 35 = 2
6,7的公倍数的个数:120 / 42 = 2
5,6,7的公倍数的个数:100 / 210 = 0
所以,重叠的炮响的次数 t = 3 + 2 + 2 - 0 = 7
所以,答案 = 1 + ( 20 × 3 - 7 ) = 54