1.1 时间复杂度

来源:NOIP2017提高组 https://ac.nowcoder.com/acm/contest/265/E

给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序,于是你的机会来啦!下面请你编写程序来判断小明对他的每个程序给出的时间复杂度是否正确。 A++ 语言的循环结构如下:
F i x y
循环体
E

然后判断 i 和 y 的大小关系,若 i 小于等于 y 则进入循环,否则不进入。每次循环结束后i都会被修改成 i +1,一旦 i 大于 y 终止循环。 x 和 y 可以是正整数(x 和 y 的大小关系不定)或变量 n。n 是一个表示数据规模的变量,在时间复杂度计算中需保留该变量而不能将其视为常数,该数远大于 100。 E表示循环体结束。循环体结束时,这个循环体新建的变量也被销毁。
注:本题中为了书写方便,在描述复杂度时,使用大写英文字母 O 表示通常意义下 \ThetaΘ 的概念。
输入描述:
输入文件第一行一个正整数 t,表示有 t(t≤ 10) 个程序需要计算时间复杂度。
每个程序我们只需抽取其中 F i x yE即可计算时间复杂度。注意:循环结构允许嵌套。
接下来每个程序的第一行包含一个正整数 L 和一个字符串,L 代表程序行数,字符串表示这个程序的复杂度,O(1)表示常数复杂度,O(n^w) 表示复杂度为 nw,其中 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 个程序,每行输出YesNo或者ERR,若程序实际复杂度与输入给出的复杂度一致则输出 Yes,不一致则输出No,若程序有语法错误(其中语法错误只有: ①F 和 E 不匹配 ②新建的变量与已经存在但未被销毁的变量重复两种情况),则输出ERR
注意:即使在程序不会执行的循环体中出现了语法错误也会编译错误,要输出ERR
示例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

输出

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≤ 10,若 x,y 均为整数,x 一定小于 y,且只有 y 有可能为 n
对于 50% 的数据:不存在语法错误,L≤ 100,且若 x,y 均为整数,x 一定小于 y,且只有 y 有可能为 n
对于 70% 的数据:不存在语法错误,L≤ 100。
对于 100% 的数据:t≤ 10,L≤ 100。若 x,y 均为整数,x 一定小于 y,且只有 y 有可能为 n

题解:

循环的时间复杂度取决于最内层的计算次数,即嵌套最深的一层循环的计算次数。
循环的嵌套和括号匹配的嵌套类似,所以我们可以借助来遍历整个代码序列。
当遇到FOR语句时,将该循环压入栈顶,当遇到END语句时,将栈顶的循环弹出。那么栈中从底到顶的序列就是当前循环从外到内嵌套的序列。

栈中存储两个类型变量名char,和截止当前的复杂度N的次数int。每次压入栈时都将上一层for循环加上,累加记录。

为何不用STL的栈因为STL的栈进行查找元素时只能每次取栈顶,想要查找就得每次删除栈顶继续,这样的话,就不对了。反而用数组可以很容易实现。。。。

对于每层循环FOR i x y,我们先判断它的计算次数time:

1、x 是 n 时:

y 也是 n,那么循环次数是 O(1);
y 是正整数,由于 n 远大于100,且 x,y 在100以内,所以这个循环一次也不执行;

2、x 是正整数时:

y 是 n,那么会循环 O(n) 次;

3、都是数字:

y 是正整数,如果 x≤y,那么会循环 O(1)次,如果 x>y,那么一次也不执行;

压入time的值

1、当前循环time没有错误并且上一次入栈的的的time没有错误(即不等于-1),则time累加
2、当有一层有错误,则其余嵌套的for都无法执行,则直接入栈,栈中的time都是-1

语法错误有两种:

1、对于当前循环创建的变量,如果在栈中已经出现过,说明与外面的某一层循环的循环变量重名了,产生语法错误;
2、如果遍历过程中对空栈执行弹出操作,产生语法错误;
3、遍历结束后栈不为空,说明FOR语句与END语句不匹配,产生语法错误。

知识点:

1、string转char转int

   string num; 
  return atoi(num.c_str());

2、 stringstream 的作用:将字符串中的字符串按空格分开再读到变量名 f,i,x,y中。

//头文件sstream
string str;
string f,i,x,y;
getline(cin,str);
stringstream sin(str);
sin>>f>>i>>x>>y;

cout<<f<<" "<<i<<" "<<x<<" "<<y<<endl;

3、栈的数组类型定义:用first和second来访问
tt的动态变化来模拟栈的变化,后面的数据覆盖存储就行,tt永远是栈的当前大小

int tt = 0;//当前栈的大小
 
pair<char,int> stk[N];

注意:一组数据结束后记得清空栈,即将 tt 置为零。

参考代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <sstream>
#include <stack>
#include <algorithm>
using namespace std;
typedef long long ll;
#define maxn 1000005
#define mod 7654321
#define NIL -1

const int N = 110;

int tt = 0;//当前栈的大小

pair<char,int> stk[N];

//获得字符串中的数字次数
int get_time(string str)
{
    //常数为n的0次
    if(str == "O(1)") return 0;

    int t = str.find('^');
    string num = str.substr(t+1,t+2);
    //string转char转int
    return atoi(num.c_str());
}

bool have(char c)
{
    for(int i=1;i<=tt;i++)
    {
        if(stk[i].first == c)
            return true;
    }
    return false;
}

//常数返回0,语法错误返回-1
int get_for_time(string x,string y)
{
    if(x == "n")
    {
        if(y == "n") return 0;
        return -1;
    }
    else
    {
        if(y == "n") return 1;
    }
    //若都是数字
    int a = atoi(x.c_str()),b = atoi(y.c_str());
    if(a <= b) return 0;
    return -1;
}

int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        int L;
        string str;
        cin>>L>>str;

        getchar();
        int O = get_time(str);
        int max_time = 0;
        bool error = false;

        while(L--)
        {
            getline(cin,str);
            //上一步出现错误则一直continue
            if(error) continue;

            if(str == "E")
            {
                //栈为空直接弹出,即数组长度减一
                //栈为空,又来一个E,则语法错误
                if(tt) tt--;
                //若栈为空 error为true时,则以后只输入,不进入下面的if
                else error = true;
            }
            else
            {
                stringstream sin(str);
                string f,i,x,y;
                //从字符串str中按空格分开,依次存储到f,i,x,y
                sin>>f>>i>>x>>y;
                //若外层循环存在当前变量名,语法错误
                //若当前语法错误,error=true则后面的if都不执行
                if(have(i[0])) error = true;
                else
                {
                    int time = get_for_time(x,y);
                    //当前循环time没有错误并且上一次入栈的的的time没有错误
                    //则time累加
                    if(time >= 0 && stk[tt].second >= 0)
                        time += stk[tt].second;
                    //若此次或上一次time有错误,则time=-1
                    //当前此层无法执行,则以后的for循环都不会执行if条件
                    //即有一层有错误,则其余嵌套的for都无法执行,则直接入栈
                    else
                        time = -1;

                    //从1开始压入栈中
                    //此时压入栈中的time是和上一层累加的结果
                    //tt的长度是动态的,若一直“E”栈为空,再遇到“F”还是从小标为1开始进行覆盖存储
                    stk[++tt] = {i[0],time};
                    //获得最大值
                    max_time = max(max_time,time);
                }
            }
        }
        //若栈不空,匹配失败,则语法错误,将栈清空
        if(tt)
            error = true,tt = 0;
        if(error) cout<<"ERR"<<endl;
        else if(max_time == O) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }

    //头文件sstream
//    string str;
//    string f,i,x,y;
//    getline(cin,str);
//    stringstream sin(str);
//    sin>>f>>i>>x>>y;
//
//    cout<<f<<" "<<i<<" "<<x<<" "<<y<<endl;

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值