【模擬-棧or遞歸-字符串】NOIP2017——時間複雜度

前言

自己一開始一直在想遞歸怎麼寫,想半天沒想清楚,還是太弱=。=

最後只得用棧模擬...有時間還是想把遞歸的寫法寫一寫,畢竟網上沒怎麼看到

題目

題目描述

小明正在學習一種新的編程語言 A++,剛學會循環語句的他激動地寫了好多程序並 給出了他自己算出的時間複雜度,可他的編程老師實在不想一個一個檢查小明的程序, 於是你的機會來啦!下面請你編寫程序來判斷小明對他的每個程序給出的時間複雜度是否正確。

A++語言的循環結構如下:

F i x y
    循環體
E

其中F i x y表示新建變量 i(變量 i 不可與未被銷燬的變量重名)並初始化爲 x, 然後判斷 i 和 y 的大小關係,若 i 小於等於 y則進入循環,否則不進入。每次循環結束後 i 都會被修改成 i+1,一旦 iii 大於 y 終止循環。

x 和 y 可以是正整數(x 和 y 的大小關係不定)或變量 n。n 是一個表示數據規模的變量,在時間複雜度計算中需保留該變量而不能將其視爲常數,該數遠大於 100。

“E”表示循環體結束。循環體結束時,這個循環體新建的變量也被銷燬。

注:本題中爲了書寫方便,在描述複雜度時,使用大寫英文字母“O”表示通常意義下“Θ”的概念。

輸入格式

輸入文件第一行一個正整數 t,表示有 t(t≤10)個程序需要計算時間複雜度。 每個程序我們只需抽取其中 F i x yE即可計算時間複雜度。注意:循環結構 允許嵌套。

接下來每個程序的第一行包含一個正整數 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, 其中 iii 是一個小寫字母(保證不爲n),表示新建的變量名,x 和 y 可能是正整數或 n ,已知若爲正整數則一定小於 100。

程序行若以E開頭,則表示循環體結束。

輸出格式

輸出文件共 t 行,對應輸入的 t 個程序,每行輸出YesNo或者ERR(輸出中不包含引號),若程序實際複雜度與輸入給出的複雜度一致則輸出Yes,不一致則輸出No,若程序有語法錯誤(其中語法錯誤只有: ① F 和 E 不匹配 ②新建的變量與已經存在但未被銷燬的變量重複兩種情況),則輸出ERR

注意:即使在程序不會執行的循環體中出現了語法錯誤也會編譯錯誤,要輸出 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

說明/提示

【輸入輸出樣例解釋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≤10,若 x、y 均 爲整數,x 一定小於 y,且只有 y 有可能爲 n。

對於 50%的數據:不存在語法錯誤,L≤100,且若 x、y 均爲整數,x 一定小於 y, 且只有 y 有可能爲 n。

對於 70%的數據:不存在語法錯誤,L≤100。

對於 100%的數據:L≤100。


如果需要Hack請私信@zhouyonglong或發討論,提供數據和能Hack掉的本題的AC記錄。

分析

最開始想到了遞歸,但是在如何實現上沒有清晰的思路,於是換個想法

如果做過一些“括號”類的題目,可以發現,此題就相當於求【最大括號嵌套深度】+判斷語法正誤

簡化了模型,這就很好辦了——可以把循環語句看做左括號,結束語句看作右括號,這樣就形成了熟悉的括號序列

例如:

6 O(n^1)

F x 9 n----------------(

F z 3 n----------------(

E-----------------------)

F y n 4----------------(

E-----------------------)

E-----------------------)

最後就形成了一個括號序列:(()())

對於它的處理應該比較熟悉了:用棧模擬,遇到左括號入棧,深度+1;遇到右括號,左括號出棧,深度-1,實時更新最大值即可

(也可以用遞歸處理)


然而這道題有些不同,它還要求“判斷實際複雜度”與“檢查語法錯誤”

(一)關於“實際複雜度”

1.正常情況下O( n ):x <= y 且 y=n

2.特殊情況O( 1 ):x > y ,包括 x 爲數字或x爲n的情況(爲了實現方便,可以把n設爲INF,這樣就都是數字了)

Ps.特殊情況時,不進入循環,但需要檢查是否有語法錯誤!

(二)關於“檢查語法錯誤”

1. F 和 E 不匹配,例如“(()” “())”

2.新建的變量與已經存在但未被銷燬的變量重複

(三)實現細節

1.讀入數據的方法可借鑑“ 快讀 ”,個人覺得很妙

2.O( 1 )的循環語句不進入,且會使其內部的所有語句都不會執行

3.一個循環結束後及時消除使用變量的標記

AC代碼

#include<cstdio>
#include<cmath>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=100,INF=0x3f3f3f3f; 
bool vis[MAXN+5];//變量名是否重複 
bool flag[MAXN+5];//是否貢獻複雜度 
int top,st[MAXN+5];
int len;//指令條數 
int w;//題目所給時間複雜度 
struct node
{
	int k;//類型:1-循環,2-結束 
	int c;//變量名 
	int x,y;//INF表示爲n 
}q[MAXN+5];
int Read()//讀入語句 
{
	char ch=getchar();int x=0;
	while((ch>'9'||ch<'0')&&ch!='n') ch=getchar();
	if(ch=='n'){getchar();return INF;}
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return x;
}
int Get_time()//讀入時間複雜度 
{
	char c=getchar();int x=0;
	while(c!='(') c=getchar();
	c=getchar();
	if(c=='1'){c=getchar();return 0;}
	while(c>'9'||c<'0') c=getchar();
	while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	c=getchar();
	return x;
} 
void Init()
{
	len=w=top=0;
	memset(st,0,sizeof(st));
	memset(vis,0,sizeof(vis));
	memset(flag,0,sizeof(flag));
	memset(q,0,sizeof(q));
}
bool Check()
{
	int top=0;
	for(int i=1;i<=len;i++)
	{
		if(q[i].k==1)//循環語句
		{
			if(!vis[q[i].c])//該變量名沒被訪問過 
			{
				st[++top]=q[i].c;//變量入棧 
				vis[st[top]]=1;//標記該變量 
			}
			else
				return false;//否則非法 
		} 
		else
		{
			if(!top)//已經沒有可匹配的語句,非法 
				return false;
			else
				vis[st[top--]]=0;//可匹配,語句刪除,變量名取消標記 
		}
	}
	return top==0;//語句未被匹配完,非法 
}
int Get_ans()//求執行程序時得到的最大時間複雜度 
{
	int now=0,ans=0;
	int stop=0;//記錄非法語句的位置,要把棧彈到這裏才能繼續執行 
	for(int i=1;i<=len;i++)
	{
		if(q[i].k==1)//循環語句 
		{
			int del=q[i].y-q[i].x;
			if(stop)//標記仍在,不能執行 
				top++;
			else if(del<0)//x>y,不能執行循環 
				stop=++top;
			else if(del<=100)//常數大小	
				top++;
			else
				now++,ans=max(ans,now),flag[++top]=true;
		} 
		else//結束語句
		{
			if(flag[top])//若加了複雜度 
				now--,flag[top]=false;//複雜度-1, 複雜度標記清空 
			if(stop&&stop==top)//若將跳出stop標記的那個循環,那麼接下來的循環可以運行,標記清空  
				stop=0;
			top--;
		} 
	}
	return ans;
}
void Work()
{
	Init();
	cin>>len;
	w=Get_time();
	for(int i=1;i<=len;i++)
	{
		char c[5];
		scanf("%s",c);
		if(c[0]=='F')
		{
			q[i].k=1;
			scanf("%s",c);
			q[i].c=c[0]-'a';
			q[i].x=Read();q[i].y=Read();
		}
		else
			q[i].k=2;
	}
	if(!Check())
	{
		printf("ERR\n");
		return ;
	}
	if(Get_ans()==w)
		printf("Yes\n");
	else
		printf("No\n");
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
		Work();
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章