[NOIP2017 提高组] 时间复杂度
题目描述
小明正在学习一种新的编程语言 A++,刚学会循环语句的他激动地写了好多程序并 给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序, 于是你的机会来啦!下面请你编写程序来判断小明对他的每个程序给出的时间复杂度是否正确。
A++语言的循环结构如下:
F i x y
循环体
E
其中F i x y表示新建变量 \(i\)(变量 \(i\) 不可与未被销毁的变量重名)并初始化为 \(x\), 然后判断 \(i\) 和 \(y\) 的大小关系,若 \(i\) 小于等于 \(y\) 则进入循环,否则不进入。每次循环结束后 \(i\) 都会被修改成 \(i +1\),一旦 \(i\) 大于 \(y\) 终止循环。
\(x\) 和 \(y\) 可以是正整数(\(x\) 和 \(y\) 的大小关系不定)或变量 \(n\)。\(n\) 是一个表示数据规模的变量,在时间复杂度计算中需保留该变量而不能将其视为常数,该数远大于 \(100\)。
E 表示循环体结束。循环体结束时,这个循环体新建的变量也被销毁。
注:本题中为了书写方便,在描述复杂度时,使用大写英文字母 \(\operatorname O\) 表示通常意义下 \(Θ\) 的概念。
输入格式
输入文件第一行一个正整数 \(t\),表示有 \(t\)(\(t \le 10\))个程序需要计算时间复杂度。 每个程序我们只需抽取其中 F i x y 和 E 即可计算时间复杂度。注意:循环结构允许嵌套。
接下来每个程序的第一行包含一个正整数 \(L\) 和一个字符串,\(L\) 代表程序行数,字符串表示这个程序的复杂度,$O(1)$ 表示常数复杂度,O(n^w) 表示复杂度为 \(n^w\),其中 \(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。
Ana
注意:即使在程序不会执行的循环体中出现了语法错误也会编译错误,要输出 ERR。
样例 #1
样例输入 #1
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
样例输出 #1
Yes
Yes
ERR
Yes
No
Yes
Yes
ERR
提示
【输入输出样例解释 \(1\)】
第一个程序 \(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 \le 10\),若 \(x\)、\(y\) 均为整数,\(x\) 一定小于 \(y\),且只有 \(y\) 有可能为 \(n\)。
对于 \(50\%\) 的数据:不存在语法错误,\(L \le 100\),且若 \(x\)、\(y\) 均为整数,\(x\) 一定小于 \(y\), 且只有 \(y\) 有可能为 \(n\)。
对于 \(70\%\) 的数据:不存在语法错误,\(L \le 100\)。
对于 \(100\%\) 的数据:\(L \le 100\)。
Analysis:
循环的时间复杂度取决于最内层的计算次数,即嵌套最深的一层循环的计算次数。
循环的嵌套和括号序列的嵌套类似,所以我们可以借助栈来遍历整个代码序列。
当遇到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语句不匹配,产生语法错误。
Code:
#include <iostream>
#include <string>
#include <algorithm>
#include <map>
#include <sstream>
using namespace std;
const int N=105;
int L,Otm,top;
string O;
pair<string,int> stk[N];
int get_time(string s)
{
if(s=="O(1)")return 0;
int res=0;
for(int i=4;i<s.size()-1;++i)
res=res*10+s[i]-'0';
return res;
}
int get_num(string a)
{
int res=0;
for(auto c:a)
res=res*10+c-'0';
return res;
}
int get_ftime(string a,string b)
{
if(a=="n")
{
if(b=="n")return 0;
return -1;
}
if(b=="n")return 1;
int fm=get_num(a),to=get_num(b);
if(fm<=to)return 0;
return -1;
}
int main()
{
int T;
cin>>T;
string str;
while(T--)
{
cin>>L>>O;
getline(cin,str);
Otm=get_time(O);
top=0;
int maxt=0;
bool err=0;
map<string,bool> mp;
for(int i=1;i<=L;++i)
{
getline(cin,str);
if(err)continue;
if(str[0]=='E')
{
if(top)
{
mp[stk[top].first]=0;
--top;
}
else err=1;
}
else
{
stringstream sin(str);
string F,I,fm,to;
sin>>F>>I>>fm>>to;
if(mp[I])err=1;
else
{
mp[I]=1;
int tm=get_ftime(fm,to);
int up_tm=stk[top].second;
if(tm>=0 && up_tm>=0)tm+=up_tm;
else tm=-1;
stk[++top]={I,tm};
maxt=max(maxt,tm);
}
}
}
if(top)err=1;
if(err)puts("ERR");
else if(maxt==Otm)puts("Yes");
else puts("No");
}
return 0;
}

浙公网安备 33010602011771号