UVA 1596 BugHunt,ACM/ICPC Tokyo2007时间限制 3000ms 内存限制 128MB
https://vjudge.net/problem/UVA-1596
这题是书上第五章的习题5-9,按照惯例书上的题就不贴详细的样例输入输出和题目描述了。
这个题首先要考虑的是题意。题目要求模拟一段程序的执行,而所执行的程序本身非常简单,操作对象是数组,数组只有赋值语句和定义语句。所以可以分开考虑赋值和定义语句;对于定义语句,我们就需要为当前执行的程序段构造一个概念上的“数组”。对于赋值语句,需要考虑左右操作数。除此之外,需要注意的地方还有,数组的下标很有可能是另一个表达式(在本题中是另一个数组的值),并且数组的下标越界或者使用了未初始化的值,都是要求输出“错误”的。
注意到这题并非需要构造一个完整的数组,因为题目要求数组的下标最大值能达到$2^{31}-1$,如果使用malloc或者new运算符在堆内存分配一个完整的数组,一是时间上不可能很快;更重要的是,可用的堆内存并不够(一般情况下在OJ中只能使用128MB的内存)。所以需要另辟蹊径。因为程序很短,使用了的数组元素有限,因此可以使用STL的map这一容器,将使用的元素进行映射(从下标到值的一个映射;一般来说,数组本身就是简单的一对一映射关系)。map容器的内部实现是红黑树,其一般操作时间为$O(lgn)$,虽然不如直接用下标访问的数组和散列表的$O(1)$时间快,但也是可以接受的。至于要求判断是否越界,则很简单,只要对这个“数组”增加一个长度的属性来存放定义时数组的长度,在每次赋值操作时检查是否越界即可。因此我在这里定义了一个结构体arrayinfo,用来存放每次新建的一个“数组”;而判断是否初始化(也就是是否赋值)也比较简单,没有赋值,则这个map中没有这个对应关系,也就是利用find()函数返回的是map的end()迭代器。经过一次赋值后,利用insert()方法插入元素,则可以表示这个下标对应的值被“初始化”了。
上面是解决这个问题的一个总体的思路:选择map这一数据容器来模拟程序中的数组。下面则主要说明一下一些细节性的问题:
一是要考虑如何创建一个“数组”。题目中的“数组”名只有一个英文字母,因此可以直接使用其ASCII码来作为下标进行定位,一旦从输入中得到定义“数组”的行为,则在arrayset这个数组中,以其数组名作为下标进行访问,用new运算符新建一个arrayinfo并关联到arrayset上。如果考虑的更符合实际一点,“数组”名字可能是一个串,那就还是需要使用map这一容器将“数组名”与实际的数组对应,具体操作便是使用一个map<string, map<int, int> * >。
第二个问题就是如何解析嵌套的数组。从一般的思维上理解,便是从外向内扒的一个过程,所以很容易写出一个递归的方法:当表达式只是常数时,作为递归出口;否则脱去最外层的数组记号,将内层的表达式继续传给自身解析,直到解析到递归出口为止。当然非递归化也是可以的,就是需要手动使用一个栈。至于写入数组的值也很简单,只要稍作修改,利用上述思路得到对应的迭代器,然后通过迭代器修改值即可。
第三个问题便是如何处理输出了。题目要求只要找到一行错误的代码,就要输出,并跳过余下的代码。我使用的是设置返回值的方法(也是传统的C/C++处理方法),利用返回代码判断是否出现错误,如果出现错误利用空循环跳过,直到读取到当前程序结束为止。因为UVA支持C++11,所以别人的代码也有直接使用try catch的结构化异常处理。从一个最早是接触VB.Net的视角来看,在这里使用异常会使得思路会显得更为清晰,这也是很多语言支持结构化异常处理的原因吧(虽然在Effective C++中,并不建议对C++使用异常)。
最后附上我的C++实现(可能是使用new delete使得效率略低,在题目输入下花费了10ms的时间):
1 #include<iostream> 2 #include<map> 3 #include<string> 4 #include<stack> 5 using namespace std; 6 struct arrayinfo 7 { 8 map<int, int> data; 9 size_t length; 10 arrayinfo(size_t len) :length(len) 11 { 12 }; 13 }; 14 arrayinfo * arrayset[256] = { 0 }; 15 int exeline = 0; 16 17 int getvalue(const string & exp) 18 { 19 //cout << " " << exp << endl; 20 if (exp[0] >= '0'&&exp[0] <= '9') 21 return atoi(exp.c_str()); 22 size_t begin = exp.find('['); 23 int id = getvalue(exp.substr(begin + 1, exp.length() - begin - 2)); 24 if (id == -1)return -1; 25 if (id >= arrayset[exp[0]]->length) 26 { 27 //subscript out of range 28 cout << exeline << endl; 29 return -1; 30 } 31 map<int, int>::iterator it = arrayset[exp[0]]->data.find(id); 32 if (it == arrayset[exp[0]]->data.end()) 33 { 34 //uninitialized variable 35 cout << exeline << endl; 36 return -1; 37 } 38 return it->second; 39 } 40 int setvalue(const string & exp, int value) 41 { 42 //cout << " " << exp << endl; 43 size_t begin = exp.find('['); 44 int id = getvalue(exp.substr(begin + 1, exp.length() - begin - 2)); 45 if (id == -1) 46 return -1; 47 if (id >= arrayset[exp[0]]->length) 48 { 49 //subscription out of range 50 cout << exeline << endl; 51 return -1; 52 } 53 map<int, int>::iterator it = arrayset[exp[0]]->data.find(id); 54 if (it == arrayset[exp[0]]->data.end()) 55 //not found, means first time to assign value 56 arrayset[exp[0]]->data.insert(pair<int, int>(id, value)); 57 else 58 it->second = value; 59 return 0; 60 } 61 62 int eval(const string & str) 63 { 64 arrayinfo * tmparray; 65 //determine whether it is declaration or assignment 66 if (arrayset[str[0]] == 0) 67 { 68 //it has not be declared . 69 tmparray = new arrayinfo(atoi(str.c_str() + 2)); //offset one letter and a '[' 70 arrayset[str[0]] = tmparray; 71 } 72 else 73 { 74 size_t aoffset = str.find('='); 75 int r = getvalue(str.substr(aoffset + 1, str.length() - aoffset)); 76 if (r == -1) 77 return -1; 78 return setvalue(str.substr(0, aoffset), r); 79 } 80 return 0; 81 } 82 83 84 85 int main() 86 { 87 //freopen("input.txt", "r", stdin); 88 string buffer; 89 int err = 0; 90 bool sdot = false; 91 while (cin >> buffer) 92 { 93 //reach a end , clear memory. 94 if (buffer[0] == '.') 95 { 96 Clear: 97 if (sdot == true) 98 break; 99 sdot = true; 100 for (int i = 0; i < 256; i++) 101 if (arrayset[i] != 0) 102 { 103 delete arrayset[i]; 104 arrayset[i] = 0; 105 } 106 exeline = 0; 107 if (err != -1) 108 cout << "0" << endl; 109 continue; 110 } 111 sdot = false; 112 ++exeline; 113 err = eval(buffer); 114 //cout << " " << err << endl; 115 if (err == -1) 116 for (;;) 117 { 118 cin >> buffer; 119 if (buffer[0] == '.') 120 goto Clear; 121 } 122 } 123 return 0; 124 }