[NOIP2017 D1 T2]时间复杂度(栈,模拟)

题目

描述

小明正在学习一种新的编程语言 A++,刚学会循环语句的他激动地写了好多程序并 给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序, 于是你的机会来啦!下面请你编写程序来判断小明对他的每个程序给出的时间复杂度是否正确。

A++语言的循环结构如下:

1
2
3
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”表示循环体结束。循环体结束时,这个循环体新建的变量也被销毁。

注:本题中为了书写方便,在描述复杂度时,使用大写英文字母“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” 。

注意:即使在程序不会执行的循环体中出现了语法错误也会编译错误,要输出 ERR。

样例输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
2
3
4
5
6
7
8
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 \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$。


解题思路

由于循环必须要让F与E配对,联想到括号匹配问题,所以用栈来模拟。
为了方便计算,我在栈里维护了三个值,分别表示当前循环所用变量名、程序执行到当前循环时的复杂度、当前循环是否处于被跳过的循环之中(即程序不会执行到)。在压栈时维护一下这三个值就好了,最后的复杂度即为最大的可被执行到的复杂度。
最后,注意一下关于字符串的读入处理就好了。


Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

int T, L, kind, w;//kind == 1:O(1); kind == 2: O(n^w)
int top, ans;
char com[100], opt[100];
bool used[1000], err;
struct Node{
char ch;
int c;//执行至此的时间复杂度
bool dir;//是否处于直接跳过的循环中
}sta[1000];

void solve(){
scanf("%d ", &L);
cin.getline(com, 99);
if(com[2] == '1') kind = 1;
else{
int now = 4;
while(com[now] >= '0' && com[now] <= '9'){
w = w * 10 + com[now] - '0';
now++;
}
kind = 2;
}
for(int i = 1; i <= L; i++){
cin.getline(opt, 99);
if(err) continue;
if(opt[0] == 'F'){
if(used[opt[2]]){ err = 1; continue; }
used[opt[2]] = 1;
int now = 4, l = 0, r = 0, pre = 0, nxt = 0;
while((opt[now] >= '0' && opt[now] <= '9') || opt[now] == 'n'){
if(opt[now] == 'n') pre = 1;
else l = l * 10 + opt[now] - '0';
now++;
}
now++;
while((opt[now] >= '0' && opt[now] <= '9') || (opt[now] == 'n')){
if(opt[now] == 'n') nxt = 1;
else r = r * 10 + opt[now] - '0';
now++;
}
if(pre == 1 && nxt == 1){
sta[top+1] = (Node){opt[2], sta[top].c, sta[top].dir};
top++;
if(sta[top].dir == 0) ans = max(ans, sta[top].c);
}
else if(pre == 1 && nxt == 0){
sta[top+1] = (Node){opt[2], sta[top].c, 1};
top++;
}
else if(pre == 0 && nxt == 1){
sta[top+1] = (Node){opt[2], sta[top].c + 1, sta[top].dir};
top++;
if(sta[top].dir == 0) ans = max(ans, sta[top].c);
}
else if(pre == 0 && nxt == 0){
if(l <= r){
sta[top+1] = (Node){opt[2], sta[top].c, sta[top].dir};
top++;
if(sta[top].dir == 0) ans = max(ans, sta[top].c);
}
else{
sta[top+1] = (Node){opt[2], sta[top].c, 1};
top++;
}
}
}
else if(opt[0] == 'E'){
if(top == 0) err = 1;
else{
used[sta[top].ch] = 0;
top--;
}
}
}
if(top) err = 1;
if(err){ puts("ERR"); return; }
if(kind == 1 && ans == 0){ puts("Yes"); return; }
if(kind == 2 && w == ans){ puts("Yes"); return; }
puts("No"); return;
}

void init(){
memset(sta, 0, sizeof sta); top = 0;
memset(com, 0, sizeof com);
memset(opt, 0, sizeof opt);
memset(used, 0, sizeof used);
err = 0;
ans = 0;
kind = w = 0;
}

int main(){
scanf("%d", &T);
while(T--){
init();
solve();
}
return 0;
}