题目 1454: [蓝桥杯][历届试题]蚂蚁感冒
时间限制: 1Sec 内存限制: 128MB
题目描述
长100厘米的细长直杆子上有n只蚂蚁。它们的头有的朝左,有的朝右。
每只蚂蚁都只能沿着杆子向前爬,速度是1厘米/秒。
当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。
这些蚂蚁中,有1只蚂蚁感冒了。并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。
请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。
输入
第一行输入一个整数n (1 < n < 50), 表示蚂蚁的总数。
接着的一行是n个用空格分开的整数 Xi (-100 < Xi < 100), Xi的绝对值,表示蚂蚁离开杆子左边端点的距离。正值表示头朝右,负值表示头朝左,数据中不会出现0值,也不会出现两只蚂蚁占用同一位置。其中,第一个数 据代表的蚂蚁感冒了。
输出
要求输出1个整数,表示最后感冒蚂蚁的数目。
样例输入
5
-10 8 -20 12 25
样例输出
3
题目分析
两只蚂蚁相遇各自反向可以看作是两个蚂蚁分别继续前进(下图来自于《挑战程序设计竞赛》第2版第一章例题Ants,题目稍有点不同,但相遇反向的概念是一样的),且所有蚂蚁前进的速度相同,不存在后面的蚂蚁追上前面的蚂蚁。
解题思路一(模拟)
- 定义蚂蚁结构体,具有属性combine和cold来表示距离方向和是否感冒。
- 通过模拟,蚂蚁每前进一米就判断每只蚂蚁是否与其他蚂蚁相遇,直到全部走出杆子。
- 若两只蚂蚁相遇,只需处理感冒传染问题,而不需要转向。
- 全部走出杆子后,统计cold为true的蚂蚁。
AC代码一
#include <cstdio>
#include <cmath>
struct ant {
int combine; // 代表数值(方向和距离的结合)
bool cold; // true代表感冒,false代表未感冒
}ants[60];
// 用于判断所有蚂蚁是否走出杆子
bool out(struct ant ants[], int n) {
for (int i = 0; i < n; ++i) {
// 若存在一只蚂蚁到竹竿左端的距离不是0或100
if (ants[i].combine != 0 && ants[i].combine != 100) {
return false;
}
}
return true;
}
int main() {
int n;
while (~scanf("%d", &n)) {
for (int i = 0; i < n; ++i) {
scanf("%d", &ants[i].combine);
// 传进来的第一个参数所代表的蚂蚁患有感冒
i == 0 ? ants[i].cold = true : ants[i].cold = false;
}
// 模拟一秒钟,每过一秒对蚂蚁是否全部out判断
while (!out(ants, n)) {
for (int i = 0; i < n; ++i) {
// 对于没有走到最左端(距离为0)或最右端(距离为100)的蚂蚁
if (ants[i].combine != 0 && ants[i].combine != 100) {
// 向左或向右前进combine都是加1
ants[i].combine++;
}
}
// 两层循环来将蚂蚁两两对比
for (int j = 0; j < n; ++j) {
for (int i = j + 1; i < n; ++i) {
// 如果两只蚂蚁之间的距离(combine相加)小于等于1,且不是位于最左端的两只蚂蚁(combine不为0),则代表两只蚂蚁相遇
if (fabs((double) (ants[j].combine + ants[i].combine)) <= 1 && ants[j].combine && ants[i].combine) {
// 只需要对两只蚂蚁是否感冒进行判断,不需要考虑转向问题
if (ants[j].cold || ants[i].cold) {
ants[j].cold = true;
ants[i].cold = true;
}
}
}
}
}
// count对感冒的蚂蚁循环计数后输出
int count = 0;
for (int k = 0; k < n; ++k) {
if (ants[k].cold) {
count++;
}
}
printf("%d\n", count);
}
return 0;
}
解题思路二(巧解)
既然两只蚂蚁相遇各自反向可以看做两只蚂蚁分别继续前进,
假设第一只蚂蚁右行,则它会感染其右边所有左行的蚂蚁,因为可看做其继续右行;
而被感染的第一只蚂蚁继续左行,感染它左边所有右行的蚂蚁。
故共存在以下两种情况:
- 若第一只蚂蚁右行,其右边所有左行蚂蚁感冒,且其左边所有右行的蚂蚁感冒;
- 若第一只蚂蚁左行,其左边所有右行蚂蚁感冒,且其右边所有左行的蚂蚁感冒。
所以只需要判断第一只蚂蚁的朝向,再统计两种情况下符合条件的蚂蚁个数即可。
AC代码二
#include <cstdio>
#include <cmath>
int main() {
int n, ants[60];
while (~scanf("%d", &n)) {
for (int i = 0; i < n; ++i) {
scanf("%d", &ants[i]);
}
int coldAnts = 1;
int first = ants[0];
for (int j = 1; j < n; ++j) {
if (first > 0) { // 如果第一只蚂蚁右行
if (fabs(ants[j]) > first && ants[j] < 0) { // 第一只蚂蚁右边左行的蚂蚁全部感冒
coldAnts++;
}
if (fabs(ants[j]) < first && ants[j] > 0) { // 第一只蚂蚁左边右行的蚂蚁全部感冒
coldAnts++;
}
} else { // 如果第一只蚂蚁左行
if (fabs(ants[j]) < fabs(first) && ants[j] > 0) { // 第一只蚂蚁左边右行的蚂蚁全部感冒
coldAnts++;
}
if (fabs(ants[j]) > fabs(first) && ants[j] < 0) { // 第一只蚂蚁右边左行的蚂蚁全部感冒
coldAnts++;
}
}
}
printf("%d\n", coldAnts);
}
return 0;
}
经验总结
- 这道题一开始做的时候就按照模拟的思路,相遇转头,但是写完代码后提交WA了。
- 起初我判断相遇的条件是
if (ants[j].combine + ants[i].combine == 0 && ants[j].combine && ants[i].combine)
。后面发现我只考虑到蚂蚁在完成1m路程后,在整数距离相遇转头;而没有考虑蚂蚁在1s中1m的路程还走完,在非整点距离相遇转头。(如7 -8加1之后变为8 -7,过程中已经相遇了)。 - 考虑到这一点之后就比较难办了,判断相遇条件需要改成相加的绝对值小于1,但是转头后的距离会变成小数而不好控制(判断到达左端的条件以及全部走出杆子的条件都要改)。
- 后来看了一些大佬的思路,理解了之后再来看自己写的代码,发现只要忽略转向这个问题,把转向代码删掉再提交就AC了。
- 之后又顺着大佬的思路自己再写一遍代码,只能感慨自己的算法思路真实太逊了,遇到问题只会想着去模拟问题,而不能打破思维定式去推理出一些细节的点,emmm加油吧。