来源:NOIP2017提高组 https://ac.nowcoder.com/acm/contest/265/E
题目描述:
给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序,于是你的机会来啦!下面请你编写程序来判断小明对他的每个程序给出的时间复杂度是否正确。 A++ 语言的循环结构如下:
F i x y
循环体
E
然后判断 i 和 y 的大小关系,若 i 小于等于 y 则进入循环,否则不进入。每次循环结束后i都会被修改成 i +1,一旦 i 大于 y 终止循环。 x 和 y 可以是正整数(x 和 y 的大小关系不定)或变量 n。n 是一个表示数据规模的变量,在时间复杂度计算中需保留该变量而不能将其视为常数,该数远大于 100。 `E`表示循环体结束。循环体结束时,这个循环体新建的变量也被销毁。
注:本题中为了书写方便,在描述复杂度时,使用大写英文字母 O 表示通常意义下 的概念。
输入描述:
输入文件第一行一个正整数 t,表示有 t(t≤ 10) 个程序需要计算时间复杂度。
每个程序我们只需抽取其中 `F i x y`和`E`即可计算时间复杂度。注意:循环结构允许嵌套。
接下来每个程序的第一行包含一个正整数 L 和一个字符串,L 代表程序行数,字符串表示这个程序的复杂度,`O(1)`表示常数复杂度,`O(n^w)` 表示复杂度为 nw,其中 w 是一个小于 100 的正整数(输入中不包含引号),输入保证复杂度只有 `O(1)` 和 `O(n^w)` 两种类型。
接下来 L 行代表程序中循环结构中的 `F i x y` 或者 `E`。 程序行若以 `F` 开头,表示进入一个循环,之后有空格分离的三个字符(串)`i x y`,其中 i 是一个小写字母(保证不为 `n` ),表示新建的变量名,x 和 y 可能是正整数或 `n` ,已知若为正整数则一定小于 100。 程序行若以 `E`开头,则表示循环体结束。
输出描述:
输出文件共 t 行,对应输入的 t 个程序,每行输出`Yes`或`No`或者`ERR`,若程序实际复杂度与输入给出的复杂度一致则输出 `Yes`,不一致则输出`No`,若程序有语法错误(其中语法错误只有: ①F 和 E 不匹配 ②新建的变量与已经存在但未被销毁的变量重复两种情况),则输出`ERR`。
注意:即使在程序不会执行的循环体中出现了语法错误也会编译错误,要输出`ERR`。
输入
8 2 O(1) F i 1 1 E 2 O(n^1) F x 1 n E 1 O(1) F x 1 n 4 O(n^2) F x 5 n F y 10 n E E 4 O(n^2) F x 9 n E F y 2 n E 4 O(n^1) F x 9 n F y n 4 E E 4 O(1) F y n 4 F x 9 n E E 4 O(n^2) F x 1 n F x 1 10 E E
输出
Yes
Yes
ERR
Yes
No
Yes
Yes
ERR
说明
第一个程序 i 从1 到 1 是常数复杂度。
第二个程序 x 从 1 到 n 是 n 的一次方的复杂度。
第三个程序有一个 `F` 开启循环却没有E结束,语法错误。
第四个程序二重循环,n 的平方的复杂度。
第五个程序两个一重循环,n 的一次方的复杂度。
第六个程序第一重循环正常,但第二重循环开始即终止(因为 n 远大于 100,100 大于 4)。
第七个程序第一重循环无法进入,故为常数复杂度。
第八个程序第二重循环中的变量 x 与第一重循环中的变量重复,出现语法错误②,输出 `ERR`。
备注:
对于 30% 的数据:不存在语法错误,数据保证小明给出的每个程序的前 L/2 行一定为以 `F` 开头的语句,第 L/2+1 行至第 L 行一定为以 `E` 开头的语句,L≤ 10,若 x,y 均为整数,x 一定小于 y,且只有 y 有可能为 `n`。
对于 50% 的数据:不存在语法错误,L≤ 100,且若 x,y 均为整数,x 一定小于 y,且只有 y 有可能为 `n`。
对于 70% 的数据:不存在语法错误,L≤ 100。
对于 100% 的数据:t≤ 10,L≤ 100。若 x,y 均为整数,x 一定小于 y,且只有 y 有可能为 `n`。
算法知识点: 栈,模拟,字符串处理
复杂度:O(TL^2)
解题思路:
循环的时间复杂度取决于最内层的计算次数,即嵌套最深的一层循环的计算次数。
循环的嵌套和括号序列的嵌套类似,所以我们可以借助栈来遍历整个代码序列。
当遇到FOR语句时,将该循环压入栈顶,当遇到END语句时,将栈顶的循环弹出。那么栈中从底到顶的序列就是当前循环从外到内嵌套的序列。
对于每层循环FOR i x y,我们先判断它的计算次数cmp:
x 是 n 时:
y 也是 n,那么循环次数是 O(1);
y 是正整数,由于 n 远大于100,且 x,y 在100以内,所以这个循环一次也不执行;
x 是正整数时:
y 是 n,那么会循环 O(n) 次;
y 是正整数,如果 x≤y,那么会循环 O(1)次,如果 x>y,那么一次也不执行;
然后判断整个循环嵌套序列的计算次数:
如果外层循环中的某一层执行次数是0或者当前循环的执行次数是0,那么当前这层的计算次数就是0;
否则当前这层的循环次数就是上一层循环的执行次数乘以前面判断出的循环次数 cmp;
语法错误有两种:
对于当前循环创建的变量,如果在栈中已经出现过,说明与外面的某一层循环的循环变量重名了,产生语法错误;
如果遍历过程中对空栈执行弹出操作,或者遍历结束后栈不为空,说明FOR语句与END语句不匹配,产生语法错误。
时间复杂度分析:
总共有 T 个测试数据,对于每个测试数据,每个循环会进栈一次,出栈一次,每次进栈之前会循环一遍栈中所有元素,判断是否存在变量重名的情况,所以总时间复杂度是 O(TL^2)。
题解代码:
1 #include <iostream> 2 #include <algorithm> 3 #include <sstream> 4 using namespace std; 5 6 typedef pair <char, int> PCI; const int N = 110; 7 8 int tt; 9 PCI stk[N]; // 栈中存储当前嵌套的所有循环 10 // first存储每一层的变量名 11 // second存储到当前这层总共的计算量,如果为-1,表示当前这层无法到达 12 13 int get_number(string str) // 将字符串转化成整数 14 { 15 int res = 0; 16 for (auto c: str) res = res * 10 + c - '0'; 17 return res; 18 } 19 20 int get_time(string str) // 提取出str中n的次数 21 { 22 if (str == "O(1)") return 0; 23 int t = str.find('^'); 24 string num = str.substr(t + 1); 25 num.pop_back(); 26 return get_number(num); 27 } 28 29 bool has(char c) // 判断当前栈中是否已经存在变量c 30 { 31 for (int i = 1; i <= tt; i++) 32 if (stk[i].first == c) 33 return true; 34 return false; 35 } 36 37 int get_cmp(string x, string y) // 判断 for (int i = x; i <= y; i ++) 的循环次数是n的多少次方 38 { 39 if (x == "n") 40 { 41 if (y == "n") return 0; 42 return -1; 43 } 44 45 if (y == "n") return 1; 46 int a = get_number(x), b = get_number(y); 47 if (a <= b) return 0; 48 return -1; 49 } 50 51 int main() 52 { 53 int T; 54 scanf("%d", &T); 55 56 while (T--) 57 { 58 int n; 59 string str; 60 cin >> n >> str; 61 int tm = get_time(str); 62 63 int max_cmp = 0; 64 bool error = false; 65 tt = 0; 66 string line; 67 getline(cin, line); 68 for (int i = 0; i < n; i++) 69 { 70 getline(cin, line); 71 72 if (!error) 73 { 74 if (line == "E") 75 { 76 if (tt) tt--; 77 else error = true; 78 } 79 else 80 { 81 stringstream sin(line); 82 string F, i, x, y; 83 sin >> F >> i >> x >> y; 84 85 if (has(i[0])) error = true; 86 else 87 { 88 int cmp = get_cmp(x, y); 89 if (!tt) stk[++tt] = { 90 i[0], cmp 91 }; 92 else 93 { 94 int computation = -1; // -1表示当前这层无法到达 95 if (stk[tt].second >= 0 && cmp >= 0) computation = stk[tt].second + cmp; 96 stk[++tt] = { 97 i[0], computation 98 }; 99 } 100 max_cmp = max(max_cmp, stk[tt].second); 101 } 102 } 103 } 104 } 105 106 if (tt) error = true; 107 108 if (error) puts("ERR"); 109 else if (tm == max_cmp) puts("Yes"); 110 else puts("No"); 111 } 112 113 return 0; 114 }
1 #include <bits/stdc++.h> 2 using namespace std; 3 class cx 4 { 5 public: 6 cx() 7 { 8 num = 0; 9 line = 0; 10 complax_rate = 0; 11 real_complax_rate = 0; 12 name.clear(); 13 nest.clear(); 14 exist_name.clear(); 15 memset(con, 0, sizeof(con)); 16 } //构造函数 17 void read(int n) 18 { 19 n=num; 20 cin >> line; //读入行数 21 string rate; 22 getline(cin,rate); 23 int num=0; 24 for(int i=0;i<rate.length();++i){ 25 if(rate[i]=='^'){ 26 for(int j=i;j<rate.length();++j){ 27 if(rate[j]>='0'&&rate[j]<='9') { 28 num*=10; 29 num+=rate[j]-'0'; 30 } 31 } 32 break; 33 } 34 } 35 complax_rate=num; 36 for (int i = 1; i <= line; ++i) 37 { 38 char ch; 39 scanf("%c", &ch); 40 if (ch == 'F') 41 { //读入为F 42 con[i] = 1; 43 string Name; 44 cin >> Name; 45 name.push_back(Name); 46 string x, y; 47 cin >> x; 48 char pxx=getchar(); 49 getline(cin,y); 50 if (x[0] == 'n' && y[0] == 'n') 51 nest.push_back(0); //从n到n,复杂度为常数 52 else if (x[0] == 'n' && y[0] != 'n') 53 nest.push_back(-1); //从n到1,不会进入 54 else if (x[0] != 'n' && y[0] == 'n') 55 nest.push_back(1); //从1到n,复杂度为n 56 else 57 { //从常数到常数 58 int _x=0, _y=0; 59 for (int i = 0; i < x.length(); ++i) 60 { 61 _x *= 10; 62 _x += x[i] - '0'; 63 } //把x变成整数 64 for (int i = 0; i < y.length(); ++i) 65 { 66 _y *= 10; 67 _y += y[i] - '0'; 68 } //把y变成整数 69 if (_x > _y) 70 nest.push_back(-1); //从大到小,不会进入 71 else 72 nest.push_back(0); //从小到大,复杂度为常数 73 } 74 } 75 else if(ch=='E') 76 { //读入为E 77 string fz; 78 getline(cin,fz); 79 con[i] = 2; 80 } 81 82 } 83 } 84 int analyze() 85 { //分析复杂度 86 int top_complax_rate = 0; //记录目前最高的复杂度 87 int now_complax_rate = 0; //记录当前复杂度 88 bool flag = true; //标记不会进入的循环 89 string flag1 = ""; //同上,第一维为不会进入的变量名,//第二维为该变量名所在的位置 90 vector<string> ::iterator it1 = name.begin(); //迭代器1 91 vector<int>::iterator it2 = nest.begin(); //迭代器2 92 vector<string>::iterator it3 = name.begin(); //迭代器3 93 for (int i = 1; i <= line; ++i) 94 { 95 if (con[i] == 1) 96 { 97 if (exist_name.count(*it1)) 98 return 3; //此时出现语法错误2 99 exist_name.insert(*it1); //将变量存入集合 100 xl_exist_name.push(make_pair(*it1, now_complax_rate)); //保存 101 int y = *it2; //方便操作 102 if (y == -1) 103 { 104 flag = false; 105 flag1 = *it1; 106 } //在这个变量弹出之前,其中所有运行复杂度均为0 107 if (y == 1) 108 { 109 if (flag != false) 110 { 111 now_complax_rate++; //更新变量的值 112 top_complax_rate = max(top_complax_rate, now_complax_rate); //更新当前最高复杂度 113 } 114 } 115 it1++; 116 it2++; 117 it3++; //移动迭代器 118 } 119 else 120 { 121 if (exist_name.empty()) 122 return 3; //此时出现语法错误1 123 else 124 { 125 string on = xl_exist_name.top().first; //记录要删去的变量 126 int ons = xl_exist_name.top().second; //ons记录去除变量后的当前复杂度 127 exist_name.erase(on); 128 xl_exist_name.pop(); //去除变量 129 if (flag == false) 130 { //如果变量处在无法进入的循环中 131 if (on == flag1) //如果这就是问题变量 132 flag1 = ""; 133 flag = true; //清空标记 134 } 135 now_complax_rate = ons; //更新复杂度 136 } 137 } 138 } 139 if(!xl_exist_name.empty()) return 3; 140 real_complax_rate = top_complax_rate; 141 return real_complax_rate == complax_rate ? 1 : 2; 142 } 143 void print(int x) 144 { 145 if (x == 1) 146 printf("Yes\n"); 147 else if (x == 2) 148 printf("No\n"); 149 else 150 printf("ERR\n"); 151 } 152 void run(int n) 153 { 154 read(n); 155 int op = analyze(); 156 print(op); 157 } 158 159 private: 160 int num; //num表示这是第几个程序 161 int line; //line表示行数 162 int complax_rate, real_complax_rate; //complax:复杂的,rate:度数,表示输入复杂度与实际复杂度 163 vector<string> name; //用于记录变量名 164 vector<int> nest; //记录该层复杂度 165 //int tier; //记录当前层数 166 int con[105]; //记录每层的操作,1为插入,2为删除 167 set<string> exist_name; //分析复杂度时,记录已经存在的变量, 168 stack<pair<string, int> > xl_exist_name; //同上,为了维护变量的顺序,第二维保存在此变量插入之前的复杂度 169 } ; 170 int main() 171 { 172 int t; 173 cin >> t; 174 for (int i = 1; i <= t; ++i) 175 { 176 cx f1; 177 f1.run(i); 178 } 179 return 0; 180 }