洛谷P3952《时间复杂度》
原更新时间:2018-10-29 23:41:46
继《玩具谜题》后的又一力作
题目描述
小明正在学习一种新的编程语言 A++,刚学会循环语句的他激动地写了好多程序并 给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序, 于是你的机会来啦!下面请你编写程序来判断小明对他的每个程序给出的时间复杂度是否正确。
A++语言的循环结构如下:
F i x y
循环体
E
其中 \(\text{F i x y}\) 表示新建变量 \(i\)(变量 \(i\) 不可与未被销毁的变量重名)并初始化为 \(x\), 然后判断 \(i\) 和 \(y\) 的大小关系,若 \(i\) 小于等于 \(y\) 则进入循环,否则不进入。每次循环结束后 \(i\) 都会被修改成 \(i+1\),一旦 \(i\) 大于 \(y\) 终止循环。
\(x\) 和 \(y\) 可以是正整数(\(x\) 和 \(y\) 的大小关系不定)或变量 \(n\)。\(n\) 是一个表示数据规模的变量,在时间复杂度计算中需保留该变量而不能将其视为常数,该数远大于 100。
“\(\text{E}\)”表示循环体结束。循环体结束时,这个循环体新建的变量也被销毁。
注:本题中为了书写方便,在描述复杂度时,使用大写英文字母“\(O\)”表示通常意义下“\(Θ\)”的概念。
Input / Output 格式 & 样例
输入格式:
输入文件第一行一个正整数 \(t\),表示有 \(t\)(\(t \le 10\))个程序需要计算时间复杂度。 每个程序我们只需抽取其中 \(\text{F i x y}\)和\(\text{E}\)即可计算时间复杂度。注意:循环结构 允许嵌套。
接下来每个程序的第一行包含一个正整数 \(L\) 和一个字符串,\(L\) 代表程序行数,字符 串表示这个程序的复杂度,O(1)
表示常数复杂度,O(n^w)
表示复杂度为\(n^w\),其中\(w\)是一个小于100的正整数(输入中不包含引号),输入保证复杂度只有O(1)
和O(n^w)
两种类型。
接下来 \(L\) 行代表程序中循环结构中的\(\text{F i x y}\)或者 \(\text{E }\)。 程序行若以\(\text{F}\)开头,表示进入一个循环,之后有空格分离的三个字符(串)\(\text{i x y}\), 其中 \(i\) 是一个小写字母(保证不为\(n\)),表示新建的变量名,\(x\) 和 \(y\) 可能是正整数或 \(n\) ,已知若为正整数则一定小于 100。
程序行若以\(E\)开头,则表示循环体结束。
输出格式:
输出文件共 \(t\) 行,对应输入的 \(t\) 个程序,每行输出Yes
或No
或者ERR
,若程序实际复杂度与输入给出的复杂度一致则输出Yes
,不一致则输出No
,若程序有语法错误(其中语法错误只有: ① \(\text{F}\) 和 \(\text{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
解题思路
首先我们肯定一眼就能看出这题是个没有任何优化的大模拟
那么如何模拟?
首先我们为了方便,把循环体离线下来,用字符串存着
根据题意,我们写一个函数GetNumber()
把字符串里的数字存下来 具体和快读差不多
我们先把小明给出的时间复杂度的\(n\)的指数记为\(\text{w}\),这里注意\(O(1)\)的情况要用\(0\)代替
接着便是求真正的时间复杂度了
首先是判断ERR 这个比较简单 我们用一个栈来储存所有的循环体的变量名
- 当\(\text{E}\)已经读完但是栈不空
- 当\(\text{E}\)未读完但是栈空
- 当储存的变量名与现在的变量名冲突
这个过程穿插在代码各处
当读到\(\text{F}\)的时候往栈里 Push 循环体变量名,注意要一块把记录变量名的数组used
进行判断并更新
之后,我们用GetNumber
获取一下\(x\)和\(y\)两个数,分情况讨论一下
- 当\(y\)是\(n\)的时候,如果这次循环可以执行,++答案
- 当\(y<x\)的时候,循环不执行,更新一下「最早不能循环的循环体」
- 剩下一种情况就是常数,可以不写
当读到\(\text{E}\)的时候,先检查栈里还有没有东西,再 Pop 出来,注意要检查一下这个变量是不是「最早不能循环的循环体」的变量
最后扫完数据,判一下栈是不是还有东西没 Pop 出来,然后验一下答案,输出
代码实现
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <stack>
using namespace std;
const int MAX = 100 + 10;
string Code[MAX];
int t;
int GetNumber(int &X, string s) {
int len = s.length();
while (!isdigit(s[X]) && X < len) {
if (s[X] == 'n') {
++X;
return 19260817;
}
++X;
}
int ret = 0;
while (isdigit(s[X])) {
ret = ret * 10 + s[X] - '0';
++X;
}
return ret;
}
int getO(string s) {
if (s[2] == 'n') {
int _ = 3; // 必须要传实参进去
return GetNumber(_, s);
}
return 0;
}
int GetO(int l) {
int ret = 0;
int now = 0;
char earliestVariant = -1; // 「最早不能循环的循环体」
int x = 0, y = 0;
stack<int> stk;
bool used[27] = { false };
bool ran[27] = { false };
for (int i = 1; i <= l; ++i) {
if (Code[i][0] == 'F') {
char varName = Code[i][2];
if (used[varName - 'a']) return -1;
stk.push(varName);
used[varName - 'a'] = true;
// Get X
int X = 4;
x = GetNumber(X, Code[i]);
// Get Y
y = GetNumber(X, Code[i]);
if (y - x > 1000) {
// y = n
if (earliestVariant == -1) {
++now;
ret = std::max(ret, now);
ran[varName - 'a'] = true;
}
} else if (x > y) {
if (earliestVariant == -1) earliestVariant = varName;
}
} else {
if (stk.empty()) return -1;
char nowVarName = stk.top();
stk.pop();
used[nowVarName - 'a'] = false;
if (earliestVariant == nowVarName) earliestVariant = -1;
if (ran[nowVarName - 'a']) {
ran[nowVarName - 'a'] = false;
--now;
}
}
}
if (!stk.empty()) return -1;
return ret;
}
int main() {
scanf("%d", &t);
while (t --> 0) {
int w, nw, l;
scanf("%d ", &l);
string o;
getline(cin, o);
nw = getO(o);
for (int i = 1; i <= l; ++i) {
getline(cin, Code[i]);
}
w = GetO(l);
if (w == -1) puts("ERR");
else {
if (w == nw) puts("Yes");
else puts("No");
}
}
return 0;
}