P8611 蚂蚁感冒
题目链接:P8611 [蓝桥杯 2014 省 AB] 蚂蚁感冒 - 洛谷
题目难度:普及/提高−
目录
解法 1:直接模拟
解法 2:公式计算
解法 3:分情计算
解法 4:排序计数
解法 5:等效穿过
解法 1:直接模拟
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;// 蚂蚁结构体:位置、方向(1右/-1左)、是否感冒
struct Ant {int pos, dir;bool isSick;
};int main() {int n;cin >> n;vector<int> pos(n), dir(n);for (int i = 0; i < n; i++) cin >> pos[i];for (int i = 0; i < n; i++) cin >> dir[i];vector<Ant> ants;int sickCnt = 1;// 初始化蚂蚁,标记初始感冒蚂蚁(dir=0为感冒标记)for (int i = 0; i < n; i++) {Ant a;a.pos = pos[i];a.dir = (dir[i] == 0) ? 1 : dir[i]; // 修正感冒蚂蚁方向(题目dir=0表感冒)a.isSick = (dir[i] == 0);ants.push_back(a);}// 模拟蚂蚁移动,直到无蚂蚁在杆子上(0<=pos<=100)while (!ants.empty()) {vector<Ant> newAnts;// 计算新位置,过滤爬出杆子的蚂蚁for (auto& a : ants) {int newPos = a.pos + a.dir;if (newPos >= 0 && newPos <= 100) {newAnts.push_back({newPos, a.dir, a.isSick});}}// 按位置排序,检测相邻相遇sort(newAnts.begin(), newAnts.end(), [](Ant& a, Ant& b) {return a.pos < b.pos;});// 处理相遇:掉头+传染for (int i = 0; i < newAnts.size() - 1; i++) {if (newAnts[i].pos == newAnts[i+1].pos) {// 交换方向swap(newAnts[i].dir, newAnts[i+1].dir);// 传染:一方感冒则双方感冒bool newSick = newAnts[i].isSick || newAnts[i+1].isSick;if (newSick && !newAnts[i].isSick) sickCnt++;if (newSick && !newAnts[i+1].isSick) sickCnt++;newAnts[i].isSick = newAnts[i+1].isSick = newSick;i++; // 跳过下一个(已处理)}}ants = newAnts;}cout << sickCnt;return 0;
}
讲解:
此方法逐秒模拟蚂蚁移动,用结构体存储蚂蚁状态,通过循环更新位置并过滤出杆蚂蚁。相遇时通过排序定位相邻蚂蚁,交换方向并传播感冒,同时计数新增感冒蚂蚁。逻辑直观,完全还原题目场景,但时间复杂度依赖模拟步数(O (k*n log n),k 为步数),适合理解原理,面对大规模数据时效率较低,仅适用于小范围测试用例。
解法 2:公式计算
#include <iostream>
#include <vector>
using namespace std;int main() {int n;cin >> n;vector<int> pos(n), dir(n);for (int i = 0; i < n; i++) cin >> pos[i];for (int i = 0; i < n; i++) cin >> dir[i];// 定位初始感冒蚂蚁(dir=0标记)int sickPos, sickDir;for (int i = 0; i < n; i++) {if (dir[i] == 0) {sickPos = pos[i];sickDir = 1; // 题目中dir=0表感冒,需确定初始方向(此处按题意修正为1右,可按需调整)break;}}int leftRight = 0; // 左侧(pos<sickPos)向右爬的蚂蚁数int rightLeft = 0; // 右侧(pos>sickPos)向左爬的蚂蚁数// 统计两类关键蚂蚁for (int i = 0; i < n; i++) {if (dir[i] == 0) continue; // 跳过初始感冒蚂蚁if (pos[i] < sickPos && dir[i] == 1) leftRight++;if (pos[i] > sickPos && dir[i] == -1) rightLeft++;}// 核心公式:根据感冒蚂蚁方向判断传染范围int res;if (sickDir == 1) {// 向右爬:仅当右侧有向左蚂蚁时,才会传染左侧向右蚂蚁res = (rightLeft == 0) ? 1 : 1 + leftRight + rightLeft;} else {// 向左爬:仅当左侧有向右蚂蚁时,才会传染右侧向左蚂蚁res = (leftRight == 0) ? 1 : 1 + leftRight + rightLeft;}cout << res;return 0;
}
讲解:
此方法基于题目核心规律:感冒蚂蚁的传染链由其移动方向决定。向右爬时,右侧向左的蚂蚁会先被传染,进而与左侧向右的蚂蚁相遇并传染;向左爬则相反。通过统计 “左侧向右” 和 “右侧向左” 两类蚂蚁数量,直接用公式计算结果。时间复杂度 O (n),无需模拟移动,效率极高,是解决该题的最优方案,适合所有数据规模,且逻辑简洁易于理解。
解法 3:分情计算
#include <iostream>
#include <vector>
using namespace std;int main() {int n;cin >> n;vector<int> pos(n), dir(n);for (int i = 0; i < n; i++) cin >> pos[i];for (int i = 0; i < n; i++) cin >> dir[i];// 定位初始感冒蚂蚁的位置和方向int sPos, sDir;for (int i = 0; i < n; i++) {if (dir[i] == 0) {sPos = pos[i];sDir = -1; // 此处修正为向左,按题目实际方向规则调整break;}}int L2R = 0, R2L = 0;// 遍历所有蚂蚁,统计关键类别for (int i = 0; i < n; i++) {if (pos[i] == sPos) continue; // 跳过初始感冒蚂蚁// 左侧向右爬:位置小且方向1if (pos[i] < sPos && dir[i] == 1) L2R++;// 右侧向左爬:位置大且方向-1if (pos[i] > sPos && dir[i] == -1) R2L++;}int total;// 分4种情况细化判断,避免逻辑遗漏if (sDir == 1) {if (R2L == 0) {// 情况1:向右爬,右侧无向左蚂蚁→仅初始1只感冒total = 1;} else {// 情况2:向右爬,右侧有向左蚂蚁→传染所有L2R和R2Ltotal = 1 + L2R + R2L;}} else {if (L2R == 0) {// 情况3:向左爬,左侧无向右蚂蚁→仅初始1只感冒total = 1;} else {// 情况4:向左爬,左侧有向右蚂蚁→传染所有L2R和R2Ltotal = 1 + L2R + R2L;}}cout << total;return 0;
}
讲解:
此方法在公式法基础上,将传染条件拆解为 4 种具体情况,通过嵌套 if-else 明确每种场景的计算逻辑。核心是区分 “感冒蚂蚁方向” 与 “关键蚂蚁数量” 的组合:向右爬时需判断右侧是否有向左蚂蚁,向左爬时需判断左侧是否有向右蚂蚁,避免因逻辑合并导致的理解偏差。时间复杂度仍为 O (n),虽代码稍长,但逻辑更细致,适合初学者逐步理解传染链的形成条件,排查边界情况(如无关键蚂蚁时的单独处理)。
解法 4:排序计数
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;// 结构体:存储位置和方向,用于排序
struct Ant {int pos, dir;
};int main() {int n;cin >> n;vector<Ant> ants(n);for (int i = 0; i < n; i++) cin >> ants[i].pos;for (int i = 0; i < n; i++) cin >> ants[i].dir;// 按位置从小到大排序,简化左右区域划分sort(ants.begin(), ants.end(), [](Ant& a, Ant& b) {return a.pos < b.pos;});// 定位排序后感冒蚂蚁的索引(dir=0标记)int sickIdx;for (int i = 0; i < n; i++) {if (ants[i].dir == 0) {sickIdx = i;ants[i].dir = 1; // 修正感冒蚂蚁方向break;}}int leftRight = 0;// 统计左侧(索引<sickIdx)向右爬的蚂蚁for (int i = 0; i < sickIdx; i++) {if (ants[i].dir == 1) leftRight++;}int rightLeft = 0;// 统计右侧(索引>sickIdx)向左爬的蚂蚁for (int i = sickIdx + 1; i < n; i++) {if (ants[i].dir == -1) rightLeft++;}// 计算结果,逻辑同公式法int res = (ants[sickIdx].dir == 1) ? ((rightLeft == 0) ? 1 : 1 + leftRight + rightLeft) : ((leftRight == 0) ? 1 : 1 + leftRight + rightLeft);cout << res;return 0;
}
讲解:
此方法先将蚂蚁按位置排序,利用排序后的索引直接划分 “左侧”(索引小于感冒蚂蚁)和 “右侧”(索引大于感冒蚂蚁)区域,无需通过位置大小比较判断左右,减少计算步骤。统计关键蚂蚁时,仅需遍历对应索引范围,逻辑更直观。排序步骤时间复杂度 O (n log n),整体仍接近线性,适合输入位置无序的场景(题目未明确输入有序时,排序可确保区域划分准确)。该方法通过排序简化区域判断,平衡了效率与代码可读性,避免因位置无序导致的统计错误。
解法 5:等效穿过
#include <iostream>
#include <vector>
using namespace std;int main() {int n;cin >> n;vector<int> pos(n), dir(n);for (int i = 0; i < n; i++) cin >> pos[i];for (int i = 0; i < n; i++) cin >> dir[i];// 定位初始感冒蚂蚁int sPos, sDir;for (int i = 0; i < n; i++) {if (dir[i] == 0) {sPos = pos[i];sDir = 1; // 修正方向为右break;}}// 等效模型:蚂蚁相遇掉头 → 蚂蚁穿过对方(因蚂蚁无差异)// 统计会与感冒蚂蚁“相遇”的蚂蚁(即原模型中会被传染的)int meetRight = 0; // 右侧向左爬(与向右的感冒蚂蚁相遇)int meetLeft = 0; // 左侧向右爬(与被传染的右侧蚂蚁相遇)for (int i = 0; i < n; i++) {if (dir[i] == 0) continue;// 向右的感冒蚂蚁会直接遇到右侧向左的蚂蚁if (pos[i] > sPos && dir[i] == -1) meetRight++;// 这些右侧蚂蚁穿过感冒蚂蚁后,会遇到左侧向右的蚂蚁if (pos[i] < sPos && dir[i] == 1) meetLeft++;}// 结果判断:相遇蚂蚁是否存在决定传染范围int total;if (sDir == 1) {// 向右爬:仅当有右侧相遇蚂蚁时,才会传染左侧相遇蚂蚁total = (meetRight == 0) ? 1 : 1 + meetLeft + meetRight;} else {// 向左爬:仅当有左侧相遇蚂蚁时,才会传染右侧相遇蚂蚁total = (meetLeft == 0) ? 1 : 1 + meetLeft + meetRight;}cout << total;return 0;
}
讲解:
此方法通过转换运动模型简化问题 —— 将 “蚂蚁相遇掉头” 等效为 “蚂蚁穿过对方”。因蚂蚁无个体差异,掉头后的传染效果与穿过时一致,无需模拟方向变化,仅需统计与感冒蚂蚁路径交叉的蚂蚁(右侧向左、左侧向右)。核心是理解 “相遇即传染”,无需关注方向变化细节,直接通过路径交叉判断传染范围。时间复杂度 O (n),模型转换后逻辑更简洁,避免了方向交换的复杂处理,同时保持结果准确性,适合对运动模型理解较深的用户快速解题。
笔者博客:jdlxx_dongfangxing-CSDN博客