UVa 1596 Bug Hunt
题目分析
本题要求我们在一个简化的编程语言中检测两种类型的错误:
- 数组索引越界:访问数组时索引不在 [0,n−1][0, n-1][0,n−1] 范围内
- 使用未赋值的元素:在表达式中引用了尚未被赋值的数组元素
输入格式
- 多组测试数据,每组以
.结束 - 整个输入以单独的
.结束 - 每行不超过 808080 个字符(不包括换行符)
- 程序不超过 100010001000 行
输出要求
对于每个程序,输出第一个出现错误的赋值语句的行号(从 111 开始计数),如果没有错误则输出 000。
解题思路
关键观察
- 语法保证正确:题目保证输入程序语法正确,数组在使用前都已声明
- 执行顺序:程序按行顺序执行,声明语句不会触发错误
- 大小写敏感:数组名区分大小写
- 大数组处理:数组长度可能达到 231−12^{31}-1231−1,不能预先初始化所有元素
算法设计
我们采用以下数据结构:
arraySize:记录每个数组的大小assigned:记录数组元素是否被赋值过arrayValues:存储数组元素的实际值
核心算法步骤:
- 读取程序:将每个数据集读入字符串数组
- 处理声明语句:记录数组大小,但不预先初始化元素(避免大数组问题)
- 处理赋值语句:
- 解析左值数组和索引表达式
- 计算右值表达式
- 在计算过程中检查索引越界和未赋值错误
- 惰性检查:只有在访问元素时才检查其状态
表达式求值
表达式可能是:
- 数字:直接转换为整数
- 数组访问:递归计算索引,然后检查边界和赋值状态
代码实现
// Bug Hunt
// UVa ID: 1596
// Verdict: Accepted
// Submission Date: 2025-10-24
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <bits/stdc++.h>using namespace std;// 存储数组名到大小的映射
map<string, int> arraySize;
// 存储数组元素的实际值
map<pair<string, int>, int> arrayValues;
// 记录数组元素是否被赋值过
map<pair<string, int>, bool> assigned;// 表达式求值函数
// 参数:expr - 表达式字符串,currentLine - 当前行号,bugLine - 引用参数,用于记录错误行号
// 返回值:表达式的值,如果出错返回 -1
int evaluateExpression(const string& expr, int currentLine, int& bugLine) {// 如果是数字,直接转换为整数if (isdigit(expr[0])) {return stoi(expr);}// 数组访问表达式:name[inner_expr]size_t bracketPos = expr.find('[');string arrayName = expr.substr(0, bracketPos);string innerExpr = expr.substr(bracketPos + 1, expr.size() - bracketPos - 2);// 递归计算内部表达式(索引)int index = evaluateExpression(innerExpr, currentLine, bugLine);if (bugLine != 0) return -1; // 如果内部表达式已出错,直接返回// 检查索引是否越界if (index < 0 || index >= arraySize[arrayName]) {bugLine = currentLine;return -1;}// 检查该元素是否被赋值过(惰性检查)if (assigned.find({arrayName, index}) == assigned.end() || !assigned[{arrayName, index}]) {bugLine = currentLine;return -1;}// 返回数组元素的实际值return arrayValues[{arrayName, index}];
}int main() {string line;// 读取多组测试数据,直到遇到单独的 "." while (getline(cin, line) && line != ".") {vector<string> programLines;programLines.push_back(line);// 读取一个完整程序,直到遇到 "."while (getline(cin, line) && line != ".") {programLines.push_back(line);}// 清空数据结构,为新程序做准备arraySize.clear();arrayValues.clear();assigned.clear();int bugLine = 0; // 记录第一个错误的行号// 逐行处理程序for (int lineNum = 0; lineNum < programLines.size(); lineNum++) {const string& statement = programLines[lineNum];size_t equalPos = statement.find('=');if (equalPos == string::npos) {// 声明语句:array_name[size]size_t bracketPos = statement.find('[');string arrayName = statement.substr(0, bracketPos);string sizeStr = statement.substr(bracketPos + 1, statement.size() - bracketPos - 2);int size = stoi(sizeStr);arraySize[arrayName] = size;// 注意:不预先初始化元素,避免大数组问题} else {// 赋值语句:left = rightstring leftPart = statement.substr(0, equalPos);string rightPart = statement.substr(equalPos + 1);// 解析左值:array_name[index_expr]size_t bracketPos = leftPart.find('[');string leftArrayName = leftPart.substr(0, bracketPos);string leftIndexExpr = leftPart.substr(bracketPos + 1, leftPart.size() - bracketPos - 2);// 计算左值索引int leftIndex = evaluateExpression(leftIndexExpr, lineNum + 1, bugLine);if (bugLine != 0) break; // 如果计算过程出错,终止处理// 检查左值索引是否越界if (leftIndex < 0 || leftIndex >= arraySize[leftArrayName]) {bugLine = lineNum + 1;break;}// 计算右值表达式int rightValue = evaluateExpression(rightPart, lineNum + 1, bugLine);if (bugLine != 0) break; // 如果计算过程出错,终止处理// 执行赋值:标记元素已赋值并存储值assigned[{leftArrayName, leftIndex}] = true;arrayValues[{leftArrayName, leftIndex}] = rightValue;}// 如果已发现错误,提前终止if (bugLine != 0) break;}// 输出结果cout << bugLine << endl;}return 0;
}
算法复杂度分析
- 时间复杂度:O(L×E)O(L \times E)O(L×E),其中 LLL 是程序行数,EEE 是表达式深度
- 空间复杂度:O(N+M)O(N + M)O(N+M),其中 NNN 是数组数量,MMM 是被赋值的元素数量
总结
本题的关键在于正确处理表达式求值过程中的错误检测,特别是对于大数组的惰性处理。通过递归求值和惰性检查,我们能够高效地检测出数组访问中的各种错误情况。
