洛谷 P3952 时间复杂度
题目所在网址:https://www.luogu.com.cn/problem/P3952
描述:
这是一道看似简单却有点恶心的题目,题目可以简单拆分为两个任务去做:
1、模拟循环嵌套并正确计算时间复杂度
2、判断所给循环是否有语法错误
接下来我们分步骤完成。
谈到嵌套、递归一类的东西,我们容易想到栈,我采用的方法是维护两个栈,一个 flags 用来存 "F"、"E" 这种东西,另外一个栈 real_complex_stack 我们用来存最外层循环到当前循环的复杂度。
本题中,单层循环复杂度只有三种:一次也不执行的O(1)、 执行常数次的O(1)、 O(n)。
根据所给的每层循环的上下限 x 和 y ,我们就能计算出每层的复杂度,参考如下函数:
int real_complex(string start, string end){//计算当前 F 的真实复杂度 if(start != "n" && end != "n" && to_int(start) < to_int(end))return 0; if(start == end)return 0; return ((start != "n" && end == "n") ? 1 : -1); }
这里我的函数返回值分别用定点数 -1、0、1代表以上三种情况。(to_int() 是自己定义的将 string 型数转为 int 型数的函数)
接下来是核心部分:
我们根据拿到的数据做如下处理:
- 定义一个最大复杂度 max_complex
- 如果拿到的是循环头(即以 "F" 开头),那么我们计算从嵌套着本层循环的最外层循环到本层循环的复杂度插入栈 real_complex_stack 中
- 如果拿到的是循环尾(即以 "E" 开头),那么我们就按 real_complex_stack 栈顶的值更新 max_complex,并将两个栈栈顶出栈
如何计算从嵌套着本层循环的最外层循环到本层循环的复杂度?参考如下重载函数:
int real_complex(int temp, int now){//temp 为从栈底到当前的总复杂度, now 为要进栈的 F 的复杂度 if(temp == -1 || now == -1) return -1; return temp + now; }
举个栗子便于理解:假设数据是这样的
则处理过程如下:
序号 | flag | 当前循环复杂度 | flags栈中内容 | flags栈操作 | real_complex_stack 栈中内容 | real_complex_stack 栈操作 | max_complex |
1 | F | 1 | - | F入栈 | - | 1入栈 | -1 |
2 | F | 1 | F | F入栈 | 1 | 计算后得到2入栈 | -1 |
3 | F | 0 | FF | F入栈 | 12 | 计算后得到2入栈 | -1 |
4 | E | - | FFF | F出栈 | 122 | 2出栈并更新max_complex | 2 |
5 | F | -1 | FF | F入栈 | 12 | 计算后得到-1入栈 | 2 |
6 | F | 1 | FFF | F入栈 | 12-1 | 计算后得到-1入栈 | 2 |
7 | E | - | FFFF | F出栈 | 12-1-1 | -1出栈并更新max_complex | 2 |
8 | E | - | FFF | F出栈 | 12-1 | -1出栈并更新max_complex | 2 |
9 | E | - | FF | F出栈 | 12 | 2出栈并更新max_complex | 2 |
10 | E | - | F | F出栈 | 1 | 1出栈并更新max_complex | 2 |
这样,接下来只需要与给定的复杂度比较就可以知道 "Yes" 还是 "No" 了。参考如下代码:
int parse_complex(string given_complex){//从给定的复杂度中解析出 n 的幂次 if(given_complex == "O(1)")return 0; string temp = ""; for(int i = 4; i < given_complex.size() - 1; i++)temp += given_complex[i]; return to_int(temp); } if(parse_complex(given_complex) == max_complex || (parse_complex(given_complex) == 0 && max_complex == -1)){ cout << "Yes" << endl; } else cout << "No" << endl;
这样,我们就解决了60%的问题。(数据还是比较规整的,前 7 组居然没有带 "ERR" 的...)
接下来,我们考虑所给循环的语法错误问题:
- 变量名重复
- E和F不匹配
对于变量名重复问题,我们可以设置一个集合 vars,每当读入"F" 的时候都检查 vars 中是否有当前的变量名,有就出错,没有就将当前变量名插入vars,每当读入"E" 的时候我们都将上一次插入集合的变量名删除。当然思想是这个思想,假如真用 set 去实现的话是没法删除的,所以这里我们可以借助题目的条件:变量名 i 只会给小写字母,用 string 充当这个集合,这样一来,删除的操作就是去掉最后一个字符。
参考如下代码:
string vars; if(flag == "F"){ if(vars.find(var) != vars.npos){//存在重复变量名 ERR(); } vars += var; } else{ vars = vars.substr(0, vars.size() - 1); }
对于E和F的匹配问题,只有两种情况:
- E比F多(E前面没有匹配的F)
- F比E多
首先,如果所给的 n 是奇数的话,那么 E 和 F 的数目肯定有问题,直接判错,接下来,针对 F 比 E 多的问题,我们可以设置一个计数器 f_num 去给 F 计数,一旦 f_num 大于 n 的一半,那就出现了
“F 比 E 多的问题”,针对 E 比 F 多的问题,我们可以在拿到 E 的时候判一下栈是否为空,如果为空,那么这个 E 前面是没有 F 和它去对应的,这就出现了“E 比 F 多的问题”。参考如下代码:
if(flag == "F"){ f_num++; if(n & 1 || f_num > n / 2){ ERR(); } } else{ if(flags.empty()){ ERR(); } }
至此,这道题我们已经解决,另外对于和我一样使用在线做法的小伙伴,还有一个坑点需要注意:循环内判断语法错误后,不要 break,应该 continue,但后面就什么也不要做了。break可能造成的问题是:这个数据点后面还有很多没有读入的,这一 break 就乱套了,所以就算程序已经判断出错,我们也应该把后面的数据读完,然后给出一个大大的 "ERR"。
最后附上完整AC代码:
- #include<iostream>
- #include<set>
- #include<stack>
- #include<cstring>
- #include<vector>
- #include<fstream>
- #include<cmath>
- #include<algorithm>
- using namespace std;
- string vars;
- stack<string>flags;
- stack<int>real_complex_stack;
- string flag, given_complex, var, start, s_end;
- bool ERR_flag;
- int parse_complex(string);
- int real_complex(string, string);
- int real_complex(int, int);
- int to_int(string);
- void ERR();
- int main(){
- // ifstream cin("data.txt");
- int t;
- cin >> t;
- while(t--){
- vars = "";
- while(!flags.empty())flags.pop();
- while(!real_complex_stack.empty())real_complex_stack.pop();
- int n, max_complex = -1, f_num = 0;
- ERR_flag = true;
- cin >> n >> given_complex;
- for(int i = 0; i < n; i++){
- cin >> flag;
- if(flag == "F"){
- f_num++;
- cin >> var >> start >> s_end;
- if(!ERR_flag)continue;
- if(n & 1 || f_num > n / 2){
- ERR();
- continue;
- }
- if(vars.find(var) != vars.npos){
- ERR();
- continue;
- }
- vars += var;
- flags.push(flag);
- int temp = real_complex(start, s_end);//当前这个 F 的复杂度
- if(real_complex_stack.empty())real_complex_stack.push(temp);
- else real_complex_stack.push(real_complex(real_complex_stack.top(), temp));
- }
- else{
- if(flags.empty()){
- ERR();
- continue;
- }
- vars = vars.substr(0, vars.size() - 1);
- max_complex = max(max_complex, real_complex_stack.top());
- real_complex_stack.pop();
- flags.pop();
- }
- }
- if(!ERR_flag)continue;
- if(parse_complex(given_complex) == max_complex || (parse_complex(given_complex) == 0 && max_complex == -1)){
- cout << "Yes" << endl;
- }
- else cout << "No" << endl;
- }
- return 0;
- }
- int parse_complex(string given_complex){//从给定的复杂度中解析出 n 的幂次
- if(given_complex == "O(1)")return 0;
- string temp = "";
- for(int i = 4; i < given_complex.size() - 1; i++)temp += given_complex[i];
- return to_int(temp);
- }
- int real_complex(string start, string end){//计算当前 F 的真实复杂度
- if(start != "n" && end != "n" && to_int(start) < to_int(end))return 0;
- if(start == end)return 0;
- return ((start != "n" && end == "n") ? 1 : -1);
- }
- int real_complex(int temp, int now){//temp 为从栈底到当前的总复杂度, now 为要进栈的 F 的复杂度
- if(temp == -1 || now == -1) return -1;
- return temp + now;
- }
- int to_int(string s){//string 数字转 int
- int temp = s[0] - '0';
- for(int i = 1; i < s.size(); i++){
- temp *= 10;
- temp += s[i] - '0';
- }
- return temp;
- }
- void ERR(){
- if(!ERR_flag)return;
- cout << "ERR" << endl;
- ERR_flag = false;
- }