2026/11/4 14:11:26

动态规划优化:仿生牛射击靶子算法解析

动态规划优化:仿生牛射击靶子算法解析 1. 问题背景与核心挑战这道题目描述了一个有趣的场景仿生牛Bessie在数轴上移动并射击靶子。我们需要通过修改最多一条命令最大化Bessie能够命中的靶子数量。这看似简单的问题实际上考察了我们对动态规划、状态转移和算法优化的深入理解。1.1 问题重述给定T个靶子的位置整数范围在[-C, C]长度为C的命令序列由L、R、F组成规则L向左移动1单位R向右移动1单位F在当前位射击若有靶子则命中并摧毁允许修改最多一条命令目标计算修改后能命中的最大靶子数1.2 关键约束分析靶子唯一性每个靶子只能被命中一次即使多次经过同一位置且射击也只能计一次命令修改限制最多只能修改一条命令数据规模T和C都可能达到1e5需要O(n)或O(nlogn)的算法2. 基础解法思路2.1 暴力解法分析最直观的想法是尝试修改每一个可能的命令然后模拟整个过程计算得分最后取最大值。这种方法的时间复杂度是O(C^2)对于C1e5的情况显然不可行。2.2 动态规划初步尝试我们可能会想到用动态规划来记录状态dp[i][j][k]处理完前i个命令已经修改了j次命令当前位置为k时的最大得分但这种状态设计的问题在于空间复杂度太高1e5 × 2 × 2e5无法有效处理靶子只能被命中一次的限制2.3 错误解法反思原文章中提到的动态规划解法dp[n][m]之所以错误正是因为它无法正确处理靶子只能被命中一次的限制。这种状态表示虽然空间复杂度可控但丢失了关键的靶子命中信息。3. 正确解法分情况处理3.1 七种修改情况分类我们需要考虑所有可能的单命令修改情况共七种不修改任何命令L→RL→FR→LR→FF→LF→R注意实际上F→F没有意义因为相当于没修改3.2 关键观察位置计算可以预先计算不修改任何命令时执行每个命令后的位置pos[i]靶子集合用哈希表记录所有靶子位置射击记录用另一个哈希表记录哪些位置的靶子已经被命中3.3 算法框架首先计算不修改任何命令时的得分然后分别处理六种修改情况计算每种情况下的最大得分最终取所有情况的最大值4. 高效实现方案4.1 预处理阶段unordered_setint target_set; // 所有靶子位置 vectorint pos(C1); // pos[i]表示执行前i个命令后的位置 pos[0] 0; for(int i0; iC; i){ pos[i1] pos[i]; if(com[i]L) pos[i1]--; if(com[i]R) pos[i1]; }4.2 不修改命令的情况unordered_setint hit; int original_score 0; for(int i0; iC; i){ if(com[i]F target_set.count(pos[i]) !hit.count(pos[i])){ hit.insert(pos[i]); original_score; } }4.3 修改命令的情况处理以L→R为例int max_score original_score; stackint fire_indices; // 记录F命令的索引 // 从后往前处理所有L命令 for(int iC-1; i0; i--){ if(com[i]F){ fire_indices.push(i); } else if(com[i]L){ // 临时修改这个L为R unordered_setint temp_hit hit; int temp_score original_score; // 计算修改后的影响 int new_pos pos[i] 1; // L→R会使位置2原本-1现在1 // 检查这个新位置是否有靶子且未被命中 if(target_set.count(new_pos) !temp_hit.count(new_pos)){ temp_score; temp_hit.insert(new_pos); } // 处理后面受影响的F命令 while(!fire_indices.empty() fire_indices.top()i){ int f_idx fire_indices.top(); fire_indices.pop(); // 原位置不再射击 if(temp_hit.count(pos[f_idx])){ temp_score--; temp_hit.erase(pos[f_idx]); } // 新位置射击 int new_f_pos pos[f_idx] 2; // 因为前面的L→R改变了路径 if(target_set.count(new_f_pos) !temp_hit.count(new_f_pos)){ temp_score; temp_hit.insert(new_f_pos); } } max_score max(max_score, temp_score); } }4.4 其他修改情况的处理其他五种修改情况的处理逻辑类似主要区别在于修改后的位置变化不同对后续F命令位置的影响不同例如R→L位置变化是-2F→L取消原射击位置变化是-1F→R取消原射击位置变化是15. 算法优化与复杂度分析5.1 关键优化点逆序处理从后往前处理命令可以高效计算修改一个命令对后续命令的影响栈的应用使用栈来管理需要调整的F命令确保每个F命令只被处理一次哈希表去重使用unordered_set来快速判断靶子是否被命中5.2 时间复杂度预处理O(C)不修改命令的情况O(C)每种修改情况O(C)每个命令最多被处理一次总复杂度O(7C) O(C)可以处理1e5规模的数据5.3 空间复杂度存储位置信息O(C)存储靶子和命中信息O(T)总空间O(C T)符合要求6. 完整代码实现#include iostream #include vector #include unordered_set #include stack #include algorithm using namespace std; int maxTargets(vectorint targets, string commands) { const int C commands.size(); unordered_setint target_set(targets.begin(), targets.end()); // 预处理位置 vectorint positions(C1); positions[0] 0; for(int i0; iC; i){ positions[i1] positions[i]; if(commands[i]L) positions[i1]--; if(commands[i]R) positions[i1]; } // 不修改命令的情况 unordered_setint original_hits; int original_score 0; for(int i0; iC; i){ if(commands[i]F target_set.count(positions[i]) !original_hits.count(positions[i])){ original_hits.insert(positions[i]); original_score; } } int max_score original_score; // 六种修改情况 vectorchar from {L,L,R,R,F,F}; vectorchar to {R,F,L,F,L,R}; vectorint offsets {2,1,-2,-1,-1,1}; // 位置变化 for(int case_id0; case_id6; case_id){ stackint fire_indices; unordered_setint temp_hits original_hits; int temp_score original_score; for(int iC-1; i0; i--){ if(commands[i]F){ fire_indices.push(i); } else if(commands[i]from[case_id]){ // 尝试修改这个命令 int new_pos positions[i]; if(to[case_id]L) new_pos--; else if(to[case_id]R) new_pos; // 如果是改为F检查能否命中 if(to[case_id]F){ if(target_set.count(new_pos) !temp_hits.count(new_pos)){ temp_score; temp_hits.insert(new_pos); } } // 处理后续受影响的F命令 while(!fire_indices.empty() fire_indices.top()i){ int f_idx fire_indices.top(); fire_indices.pop(); // 原位置不再射击 if(temp_hits.count(positions[f_idx])){ temp_score--; temp_hits.erase(positions[f_idx]); } // 新位置射击 int new_f_pos positions[f_idx] offsets[case_id]; if(target_set.count(new_f_pos) !temp_hits.count(new_f_pos)){ temp_score; temp_hits.insert(new_f_pos); } } max_score max(max_score, temp_score); } } } return max_score; } int main() { int T, C; cin T C; vectorint targets(T); for(int i0; iT; i) cin targets[i]; string commands; cin commands; cout maxTargets(targets, commands) endl; return 0; }7. 测试用例验证7.1 样例1验证输入3 7 0 -1 1 LFFRFRR处理过程不修改命令命中位置-1和0得分为2修改最后一个R为F额外命中位置1得分为3 输出3正确7.2 样例2验证输入1 5 0 FFFFF处理过程不修改命令第一次F命中0后续F无效得分为1任何修改都无法增加得分 输出1正确7.3 样例3验证输入5 6 1 2 3 4 5 FFRFRF处理过程不修改命令命中位置1,3,5得分为3尝试各种修改后发现无法增加得分 输出3正确7.4 边界测试单个靶子单个命令1 1 0 F输出1无靶子情况0 5 (空行) FFFRR输出08. 常见问题与调试技巧8.1 问题排查清单得分计算错误检查靶子命中是否去重验证位置计算是否正确确认修改命令后的位置变化是否正确性能问题确保没有不必要的嵌套循环使用合适的数据结构unordered_set避免重复计算特殊用例处理无靶子情况所有命令都是移动无射击靶子位置超出最终移动范围8.2 调试技巧打印中间状态cout After command i ( commands[i] ), pos positions[i] endl;可视化路径 绘制命令执行的位置变化图标记射击点和靶子位置单元测试 编写小规模测试用例逐步验证每种修改情况8.3 性能优化建议减少拷贝使用引用传递大数据结构提前终止当得分已经达到最大可能值时提前结束计算位压缩如果靶子位置范围小可以用bitset代替哈希表9. 算法扩展思考9.1 问题变种允许修改k条命令需要更复杂的状态表示可能要用动态规划靶子可重复命中简化问题只需统计经过靶子位置的F命令数量移动步长可变增加问题复杂度需要调整位置计算方式9.2 实际应用这类问题在实际中有多种应用场景机器人路径规划最大化收集目标点游戏AI设计优化角色行动序列自动化测试优化测试用例执行顺序9.3 学习建议掌握基础熟练动态规划、贪心算法等基本范式分析问题学会将复杂问题分解为多个简单情况优化思维培养对算法时间/空间复杂度的敏感度这道题目很好地展示了如何将一个看似复杂的问题通过系统分析和分类处理转化为可高效解决的算法问题。关键在于识别问题的核心约束单次修改、靶子唯一性并设计相应的数据结构来跟踪必要的信息。