NOIP2017时间复杂度

时间复杂度

(涉及算法:模拟)

【问题描述(简要)】

小明写了好多循环并给出了时间复杂度,判断小明给出的时间复杂度是否正确。循环结构如下:

F i x y
循环体
E

其中“F i x y”表示
新建变量 i(变量 i 不可与未被销毁的变量重名)
初始化为 x
然后判断 i 和 y 的大小关系,若 i小于等于 y 则进入循环,否则不进入。
每次循环结束后 i都会被修改成i+1。

x 和 y 可以是正整数(x 和 y 的大小关系不定)或变量 n。
n 是一个变量,在时间复杂度计算中需保留该变量而不能将其视为常数,该数远大于 100。
“E”表示循环体结束。循环体结束时,这个循环体新建的变量也被销毁。

【输入格式】

输入文件名为 complexity.in。
输入文件第一行一个正整数 t,表示有 t(t≤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”开头,则表示循环体结束。

【输出格式】

输出文件名为 complexity.out。
输出文件共 t行,对应输入的 t 个程序,每行输出“Yes”或“No”或者“ERR”(输出中不包含引号)
若程序实际复杂度与输入给出的复杂度一致则输出“Yes”,不一致则输出“No”,
若程序有语法错误则输出“ERR”。
① F 和 E 不匹配
②新建的变量与已经存在但未被销毁的变量重复两种情况)
注意:即使在程序不会执行的循环体中出现了语法错误也会编译错误,要输出“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

【样例说明】

第一个程序 i从 1 到 1是常数复杂度。
第二个程序 x 从 1 到 n 是 n 的一次方的复杂度。
第三个程序有一个 F 开启循环却没有 E 结束,语法错误。
第四个程序二重循环,n 的平方的复杂度。
第五个程序两个一重循环,n 的一次方的复杂度。
第六个程序第一重循环正常,但第二重循环开始即终止(因为 n 远大于 100,100 大于 4)。
第七个程序第一重循环无法进入,故为常数复杂度。
第八个程序第二重循环中的变量 xx 与第一重循环中的变量重复,出现语法错误②,输出 ERR。

【数据规模与约定】

对于 30%的数据:不存在语法错误,数据保证每个程序的前 L2 行一定为以 F 开头的语句,第 L2+1 行至第 L行一定为以 E 开头的语句,L≤10,若 x、y 均为整数,x 一定小于 y,且只有 y 有可能为 n。
对于 50%的数据:不存在语法错误,L≤100,若 x、y 均为整数,x 一定小于 y,且只有 y 有可能为 n。
对于 70% 的数据:不存在语法错误,L≤100。
对于 100% 的数据:L≤100。
时间限制:1s
空间限制:256MB

【题解】

感觉我的代码挺短的……(他们说长)
主要讲一下思路,具体做法注释在代码里面。
这是一个关于循环的复杂度的问题。 首先分析答案,答案有三种情况:
1.答案正确

2.答案错误
那么必然要存下小明的答案,用num1,再用ans表示正确复杂度
因为复杂度只有O(1)和O(n^w )两种情况,所以用0表示O(1),正整数w表示O(n^w)

3.程序错误
需要一个bool来判断指针是否错误,如果错误,只需输入数据后退出就可以,用flag
程序错误又分两种情况
① F 和 E 不匹配
sum1sum2记下两者的数量,最后判断两者数量是否相同即可
也可以用一个sum记录,最后判断它是否大于0即可
②新建的变量与已经存在但未被销毁的变量重复两种情况
那么就需要一个bool数组psx来表示这个变量是否已经存在
众所周知,循环是一层一层跳出去的,所以这个变量必然是存在顺序的,再用chan数组记录变量的顺序,方便退出循环时对变量进行撤回操作。
注意:即使在程序不会执行的循环体中出现了语法错误也会编译错误,要输出“ERR”。
这句话提醒我们会出现循环不进入的情况。
如果一层循环没有进入,那么它下面包含的所有循环都不会进入,也就不需要对他们进行操作。
用一个fl来记录没有进入的循环有几层,如果轮到一层循环时,fl不为0,那么这层循环必然也没有进入。遇到一个不能进入的循环就fl++,遇见一个E,退出了一层不进入的循环,就fl- -。
又因为即使在程序不会执行的循环体中出现了语法错误也会编译错误,所以把对变量重名的判断放在对不进入循环的判断前面就可以了。

然后对循环的情况进行判断,用一个zz来记录循环的复杂度
1.常数小——常数大
复杂度是O(1),因为O(1)表示为0.
这时候这个循环是有效的,但是它不会对复杂度产生影响,我们令mm++;
mm有什么用,下面会详细说。
2.常数大——常数小
这个循环不进入,使fl++
3.常数——n
复杂度多了一个n,zz++
4.n——常数
这个循环不进入,使fl++

以上是碰到F的做法

如果碰上E,需要做三件事
1.变量使用的撤回,将最后那个循环使用的变量记录为没有使用
2.如果fl不为0,使fl- -
3.如果zz不为0,对正确答案ans进行更新。
如果mm为0,zz- -,这里zz不清零,因为只是退出了一层循环
如果mm不为0,就表示上一层是(常数小——常数大)的循环
它的存在并没有对复杂度有影响,即zz 并没有因为这个循环而加1,所以这个E退出的时候,zz并不会减1.

思考完了,代码实现就不难了,注意一些小细节即可,尤其是字符串转成数字

关于mm的问题是salt___fish提出的,以下数据可以卡走没有mm的代码
1
10
O(n^3)
F i 1 n
F k 3 10
E
F j 2 n
F k 3 10
E
F k 3 n
E
E
E

【代码】

#include
#define ll long long
using namespace std;
int t,l,zz,mm,ans,fl,num1,sum1,sum2,tot=0,x,y;
string s,s1,s2;
char ch,chan[105]={};
bool psx[100]={},flag;
void clean(){
    flag = 1;//判断代码是否ERR,0是错误,1是正确
    num1 = 0;//记录小明自己的结果
    zz=0;//循环没有结束时记录复杂度的暂时容器
    mm=0;//记入有效但是不计入zz的常数到常数循环,防止将zz错减
    ans=0;//正确的复杂度
    fl=0;//判断没有进入的循环
    sum1=0;//F的数量
    sum2=0;//E的数量
    memset(psx,0,sizeof(psx));//用过的变量名
    tot = 0;
}
int main() {
    //freopen("complexity.in","r",stdin);
    //freopen("complexity.out","w",stdout);
    cin>>t;
    while(t--){
    	clean();
    	cin>>l>>s;
    	if(l % 2) flag = 0;//如果代码长度是奇数,那么F与E的数量一定不一样,错误
    	if(s.size() == 4) num1 = 0;//O(1)的情况
    	else for(int i = 4; i < s.size()-1; i++)
    			num1 = num1 * 10 + int(s[i] - '0');
    	//因为w小于100,所以要用循环,我就摔在这
    	while(l--){
    		cin>>ch;
    		if(ch == 'F'){
    			sum1++;
    			cin>>chan[++tot];//因为循环一层层退出,所以字母要有顺序
    			if(psx[chan[tot]-'a'+1] == 1) flag = 0;//如果字母已出现,程序错误
    			psx[chan[tot]-'a'+1] = 1;//记录字母
    			cin>>s1>>s2;//输入字符串,因为x和y有可能是n,不能用int,char读不尽
    			if(!flag) continue;//输入的都输入了,如果程序错误,退出继续输入
    			if(fl){fl++;continue;}//如果上一层循环没有进入,那么这一层也一定没有进
    			if(s1[0] == 'n' && s2[0] != 'n') {fl++;continue;}//因为n远大于吗100,循环不进去
    	        if(s1[0] != 'n' && s2[0] == 'n') zz++;//出现一层n,zz++(这里的1是指n的次方加一)
    	        if(s1[0] != 'n' && s2[0] != 'n'){//如果两个常数
	    	        if(s1.size() > s2.size()) {fl++;continue;}//不进入循环
	    	        mm++;
	    	        if(s1.size() == s2.size())
	    	          	for(int i = 0; i < s1.size(); i++){
	    	            	if(s1[i] > s2[i]) {mm--;fl++;continue;}
	    	            	if(s1[i] < s2[i]) break;
	    	            }//比较大小,因为数不是一位数,要循环
    	   		}
    		}
			if(ch == 'E'){
				if(!flag) continue;//如果程序错误退出
				sum2++;
	        	psx[chan[tot--]-'a'+1] = 0;//字母可以用了
				if(fl){fl--;continue;}//如果程序没有进入的,就往外退
				if(zz > 0){ans = max(ans,zz);!mm ? zz-- : mm--;} 
				//如果zz大于0,就更新ans,只是退出一层循环,所以zz--,而不是清零
			}
		}
		if(!flag || sum1 != sum2 || fl) printf("ERR\n");
		//如果程序错误,E和F数量不一样,没进入的程序还没有退出完
		else if(ans == num1) printf("Yes\n");//如果小明答案和正确答案一样
		else printf("No\n");
    }
	return 0;
}

你可能感兴趣的:(#,复赛真题)