[NOIP2017]时间复杂度
皎月半洒花
2018-05-02 12:52:30
这个题是我 $A$ 的第三百个……那么首先:
## $\color{pink}AC300$祭!๑乛◡乛๑
嗯,那么下面开始干正事:
## 一、对于这个题的概述
先说明一下,这个题虽然我花了大约两个上午——大约 $5h$ 的时间把它连码带调搞出来了,好像看上去很毒瘤,但是其实这个题根本不毒瘤,只是有些细节值得注意罢了。
不得不说,这是道很不错的题,因为既没有多么难,规则浅显易懂,也会有很多的细节不注意导致 GG(本蒟蒻就几乎把所有这个题能得的分数遍历了一遍 ORZ,多亏可以下载数据……不过考试的时候好像肯定会 GG)。
那么对于这道题我在做的时候就发现,出题人真的是太良心了,因为以下几点完全就是怕 NOIP 出的太难,来均衡难度的:
### 1 循环变量的上下界只有一个字母会出现:$n$。
但是从渐进意义上来讲,任何一个字母都是可以的,而这个地方只出现了 $n$,所以这是送分的。
#### 毒瘤版本:
循环的上下界可以有别的字母,那么以下这种情况也需要考虑进总的时间复杂度,按照 $O(n)$ 算:
```cpp
F i x y
F j 1 i
E
E
```
那么这个时候就要牵扯到除了 $n$ 以外的其他字母实现有没有被定义(如果未定义输出 `ERR`)等诸多问题,比如可能这样:
```cpp
F i 3 66
F j 1 i-1
F k i j
E
E
E
```
那么实际上,在上面这组样例中,$k$这层循环是进不去的。
### 2 所有常数都小于一百
这一点也很良心,由于都小于一百,所以我们在输入或者其他操作里,只用考虑两位就可以。不要小瞧这一点……这直接决定了模拟的难度。
#### 毒瘤版本:
在每个循环中,循环变量的绝对值保证在 `int` 范围里。
### 3、时间复杂度方面,不会出现$log$以二为底
嗯,以上就是本蒟蒻 yy 出来的毒瘤版本,那么看起来这道题不算是个毒瘤题……但为什么会做错呢?因为细节不注意啊 ORZ。
## 二、这个题的做法
我还是倒着说吧:
### 满分做法:
**读入**:
我们先用 `while` 按字符读入每个程序的第一行,抠出需要检验的复杂度, $O(1)$ 用 $0$ 来存 $[n^0=1 (n\neq 0)]$。
注意,有可能有两位数,需要多扣一位……
```cpp
while(o!=')'){
if(o=='1'&&!chk)
need_check=0;
if(o=='n'){
cin>>o>>o;
need_check=o-48;
chk=1;
}
o=getchar();
if(isdigit(o)&&chk)need_check*=10,need_check+=(o-48);
}
getchar();
```
至于最后为什么要再 `getchar()` 一次……自己试试就知道了。
那么接下来就要按行读入循环了,比较简单。
**初始化**
为了使码风简洁,所以写到函数里了。这个地方我用到了三个栈,一个用来记录每个循环的答案(因为有可能有多个相互独立的循环),一个用来记录每次 `F` 时读入的循环上下界。以上两个都是 `int` 栈,还有一个 `char` 栈,存储每次定义的循环变量,而这个字符栈搭配一个 `bool` 性的数组,用于记录是否可用。
```cpp
#define MAX 1000000
int i,x,y,t,tt,num,cntf,cnte,res,ans[MAX],T,l,now,need_checks,stk[MAX];
bool check[150],flag,spj,chk;
char s[3010],o,stkk[MAX];
inline void init(){
memset(check,0,sizeof(check));
memset(stkk,0,sizeof(stkk));
memset(stk,0,sizeof(stk));
now=t=tt=cntf=cnte=res=flag=spj=chk=0;
}
```
ps: 虽然不知道用一个二进制位的$0$来初始化字符数组会怎样……不过好像海星。
`cnte` 和 `cntf` 用来记录 `F` 和 `E` 的数量, `num` 、 `t` 、 `tt` 都是栈的指针,`spj` 用来判断一个独立循环是否结束(如果结束就把当前的得到压入栈),`now` 用来搭配 `spj` 记录当前独立循环体的时间复杂度, `chk` 用于读入每个程序的第一行(即含有需要判断的时间复杂度的那一行),`flag` 用于判断输出。
**主要操作**
对于读入的东西,分类讨论,然后 `continue`……没什么可说的。
```cpp
while(l--){
gets(s);
for(i=0;i<strlen(s);){
while(s[i]==' ')i++;
if(s[i]=='F'){
if(cntf>cnte&&cnte){
ans[++num]=now;
now=0;
}
cntf++,i++;
continue;
}
if(s[i]=='E'){
y=stk[t],t--;
x=stk[t],t--;
cnte++;
if(cnte==cntf)spj=1;
if(x!=MAX){
if(y==MAX)now++;
if(y<x)now=0;
}
else{
if(y!=MAX)now=0;
}
check[stkk[tt]-'a']=0;
tt--,i++;
if(spj){
ans[++num]=now;
now=0;
spj=0;
}
continue;
}
if(!isdigit(s[i])&&s[i]!='n'){
if(check[s[i]-'a']&&!flag){
cout<<"ERR"<<endl;
flag=1;
}
stkk[++tt]=s[i];
check[s[i]-'a']=1;
}
else {
if(s[i]=='n'){
my_push(s[i],s[i+1]);
i+=2;
}
my_push(s[i],s[i+1]);
}
i++;
}
}
```
唯一需要注意的是入栈操作,因为要把字符压入整型,所以我又写了个函数来入栈。入栈的时候当然需要注意是不是两位数……
哦,还有,如果这次轮到 $n$ 入栈了,那么就随便入栈一个大于一百的数即可。
```cpp
inline void my_push(char a,char b){
if(isdigit(a)){
if(isdigit(b)){
stk[++t]=(a-48)*10+b-48;
i++;
}
else stk[++t]=a-48;
}
if(a=='n')stk[++t]=MAX;
}
```
最后判断一下输入输出即可。
```cpp
if(!flag&&cntf!=cnte){
cout<<"ERR"<<endl;
flag=1;
}
while(num){
res=max(res,ans[num]);
num--;
}
if(!flag) if(res==need_check) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
```
嗯~o(* ̄▽ ̄*)o这就是满分做法了。
### $72$ 分做法
这个很坑爹……输出完直接 `break` 会导致你虽然在主观上已经不管这个询问接下来如何,但是客观上,你的程序是一边输入一边操作的,所以你接下来的输入就会 GG,这也就是引入 `mark` 的原因。
### $63$ 分做法
在 72 分的基础上,再输入 `need_check` 忘记判断 $O(n^{11})$ 和 $O(1)$ 了……这就很爆炸。
### $45$ 分做法
没有考虑嵌套在循环中的循环体的独立性,那么我们可以发现,当你在读入一个 `F` 时,如果现在 `F` 的数量大于 `E` 的数量并且 `cnte!=0`,就说明是这种情况那么就提前将结果入栈即可。
```cpp
if(s[i]=='F'){
if(cntf>cnte&&cnte){
ans[++num]=now;//提前入栈
now=0;
}
cntf++,i++;
continue;
}
```
### $27$ 分做法
忘记了自然数可以是两位数……所以赶紧打了个入栈。
### $0$ 分做法
全部的爆零都是因为我忘记了把调试用的 `freopen` 注释掉 ORZ。
总结:我认为这个题我在考场上挂的情有可原……因为我既蒟又不细心 OTZ。
还有,如果那位大佬 `hack` 掉了我的程序,别忘了通知我哇!
```cpp
#include<bits/stdc++.h>
#define MAX 1000000
using namespace std;
int i,x,y,t,tt,num,cntf,cnte,res,ans[MAX],T,l,now,need_check,stk[MAX];
bool check[150],flag,spj,chk;
char s[3010],o,stkk[MAX];
inline void init(){
memset(check,0,sizeof(check));
memset(stkk,0,sizeof(stkk));
memset(stk,0,sizeof(stk));
now=t=tt=cntf=cnte=res=flag=spj=chk=0;
}
inline void my_push(char a,char b){
if(isdigit(a)){
if(isdigit(b)){
stk[++t]=(a-48)*10+b-48;
i++;
}
else stk[++t]=a-48;
}
if(a=='n')stk[++t]=MAX;
}
int main(){
cin>>T;
while(T--){
init();
cin>>l>>o;
while(o!=')'){
if(o=='1'&&!chk)
need_check=0;
if(o=='n'){
cin>>o>>o;
need_check=o-48;//the same as above
chk=1;
}
o=getchar();
if(isdigit(o)&&chk)need_check*=10,need_check+=(o-48);
}
getchar();
while(l--){
gets(s);
for(i=0;i<strlen(s);){
while(s[i]==' ')i++;
if(s[i]=='F'){
if(cntf>cnte&&cnte){
ans[++num]=now;
now=0;
}
cntf++,i++;
continue;
}
if(s[i]=='E'){
y=stk[t],t--;
x=stk[t],t--;
cnte++;
if(cnte==cntf)spj=1;
if(x!=MAX){
if(y==MAX)now++;
if(y<x)now=0;
}
else{
if(y!=MAX)now=0;
}
check[stkk[tt]-'a']=0;
tt--,i++;
if(spj){
ans[++num]=now;
now=0;
spj=0;
}
continue;
}
if(!isdigit(s[i])&&s[i]!='n'){
if(check[s[i]-'a']&&!flag){
cout<<"ERR"<<endl;
flag=1;
}
stkk[++tt]=s[i];
check[s[i]-'a']=1;
}
else {
if(s[i]=='n'){
my_push(s[i],s[i+1]);
i+=2;
}
my_push(s[i],s[i+1]);
}
i++;
}
}
if(!flag&&cntf!=cnte){
cout<<"ERR"<<endl;
flag=1;
}
while(num){
res=max(res,ans[num]);
num--;
}
if(!flag) if(res==need_check) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
```