1. 程式人生 > >NOIP 2017 Day1 T2 時間複雜度 complexity - 模擬題 題解

NOIP 2017 Day1 T2 時間複雜度 complexity - 模擬題 題解

作者@豪噠噠噠HaoDaDaDa
轉載自簡書@豪噠噠噠HaoDaDaDa-簡書-NOIP 2017 Day1 T2 時間複雜度

(有一個月沒有寫簡書了…)
(這次終於開始拿Markdown寫了,富文字可以走開了)
說說為什麼我要寫這道題…
因為…dalao們寫的都是兩百多行的程式碼看不懂
於是我決定自己寫一篇…
也是第一次拿markdown寫
錯誤連篇請原諒
原諒我可好 我傲慢的青春

題目索引 [NOIP 2017 Day1 T2 時間複雜度]

LOJ.ac #2315
loj老卡,可能我家網不好

題面如下

都給我老老實實看題 ,[不想看題的戳這][1]

題目描述

給出了他自己算出的時間複雜度,可他的程式設計老師實在不想一個一個檢查小明的程式,於是你的機會來啦!下面請你編寫程式來判斷小明對他的每個程式給出的時間複雜度是否正確。 A++ 語言的迴圈結構如下:

F i x y
    迴圈體
E

然後判斷 i 和 y 的大小關係,若 i 小於等於 y 則進入迴圈,否則不進入。每次迴圈結束後 i 都會被修改成 i+1,一旦 i 大於 y 終止迴圈。 x 和 y 可以是正整數(x 和 y 的大小關係不定)或變數 n。n 是一個表示資料規模的變數,在時間複雜度計算中需保留該變數而不能將其視為常數,該數遠大於 100。 E表示迴圈體結束。迴圈體結束時,這個迴圈體新建的變數也被銷燬。

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

輸入格式

輸入檔案第一行一個正整數 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,其中 iii 是一個小寫字母(保證不為 n ),表示新建的變數名,x 和 y 可能是正整數或 n ,已知若為正整數則一定小於 100。 程式行若以 E開頭,則表示迴圈體結束。

樣例
樣例輸入~~(好心的我已經tab好了)~~
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。
資料範圍與提示
1.對於 30%的資料:不存在語法錯誤,資料保證小明給出的每個程式的前 L/2 行一定為以 F 開頭的語句,第L/2+1 行至第 L 行一定為以 E 開頭的語句,L≤10,若 x,y均為整數,x 一定小於 y,且只有 y 有可能為 n。

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

3.對於 70% 的資料:不存在語法錯誤,L≤100。

4.對於 100% 的資料:t≤10,L≤100。

(話說題目好長真的懶得看)

題解…

題目分析

很明顯這是一道不需要智商但是打起來賊噁心的模擬題

我們先來看一看題發現它的語言是A++而且只有F迴圈結構

根據我們的常識知道這裡多半要壓棧因為我們需要知道當前的F迴圈是哪一個

然後判斷一下有沒有語法錯誤就好啦!!!

好了說正經的,五步寫完

1.資料準備

int T,l,on,Fnum,Maxon;//T程式數,l程式行數,on當前複雜度,Fnum當前程式F-E個數,Maxon當前程式最大複雜度 
string On,Ans;//輸入,答案 
bool isError;//當前程式錯誤開關 
const int INF=-99999;//進不去的迴圈

2.準備一個函式,檢查當前複雜度與期望複雜度是否一致

注意: 期望 O(1)即為O(n^0),w=0
(string) On 為期望複雜度(輸入)
然後機智的我們可以發現在
(string) On 中期望複雜度只需要 取出(On[4]~On[On.length()-1])所表示數就好了
最後 return r==w;

bool checkRight(int w){// 檢查複雜度是否一置 
	if(w==0&&On=="O(1)") return 1; 
	else {
		int r=0;
		for(int i=4;i<On.length()-1;i++) {//這個for實際上就是從(string)On裡取出數字來 
			r*=10;
			r+=On[i]-'0';
		}
		return r==w;
	}
}

3.再準備一個函式,獲取某個F迴圈的複雜度

注意:一個F迴圈只有可能存在三種複雜度:0 , 1 , INF
INF意思看上邊
這裡我們簡單記 F(s,e)為從 s一直迴圈加1到 e 的複雜度
設const_為某常數(據題const_<=100)

when s=e=n 時 , 迴圈可以進入一次故 F(n,n)=0;
when s=const_,e=n 時 , 迴圈需要走n次(因為n遠大於const_) ,F(const_,n)=1
when s=n,e=const_ 時 , 迴圈無法進入 (因為const_遠小於n) ,F(n,const_)=INF負無窮大
3個判斷後,s,e均為常數
when const_1<=const_2 時 ,F(const_1,const_2)=0;
when const1>const_2 時 , 迴圈無法進入,F(const_1,const_2)=INF負無窮大

code↓↓↓
同理,兩個for從string中取數,不詳講

int getOn(string x,string y){//獲取複雜度 
    if(x!="n"&&y=="n") return 1;//從 常數 到 n :複雜度為 1 
    if(x=="n"&&y!="n") return INF;//從 n到常數 :由於無法進入故認為其複雜度為負無窮  依據題目 -99999 已經足夠 
    if(x=="n"&&y=="n") return 0;//從 n到 n相當於 常數 到 常數 複雜度為 0 
    
    //x y 均為數字 ,則只要判斷迴圈能否進入即可,能進入 複雜度為0 , 不能進入 複雜度則為 負無窮(INF) 
    int xx=0,yy=0;
    for(int i=0;i<x.length();i++){
        xx*=10;
        xx+=x[i]-'0';
    }
    for(int i=0;i<y.length();i++){
        yy*=10;
        yy+=y[i]-'0';
    }
    return yy>=xx?0:INF;//yy>=xx 常數級別  
}

4.入讀資料

(讀入完後的工作以 work()省略)
map<Object , Object > map_name用法詳細見 link[… ]

scanf("%d",&T);
    while(T--){//小明寫了n個 A++ 程式 
        map<string ,int >map_times;//關聯容器 將變數名與次數關聯,可與下邊胡map_on合併 
        map<string ,int >map_on;//儲存變數的複雜度 
        stack<string >st;// top() 表示當前所在胡迴圈 
        isError=false;//錯誤開關關閉 
        Fnum=Maxon=on=0;//F-E記次清零 最大複雜度,當前複雜的清零 
        cin>>l>>On;//獲取每個程式初始資料 行數與期望複雜度 
        string F,i,x,y;//準備 迴圈輸入資料 
        while(l--){//程式行數 
            cin>>F;
            if(F=="F"){//首字母檢測 F則開始迴圈 E則退出 
                cin>>i>>x>>y;
                work_1();
            }else{//首字母為 E
            	work_2();
            }
        }
        cout<<Ans<<endl;//輸出答案 
    }

5.work()

好吧這道題噁心也就在ERR的時候
分析一下 ERR有這麼幾種情況~~(重複請忽視)~~

1. 程式行數L為奇數,則F,E肯定不匹配 (F,E個數不匹配)
2. F,E 出入棧不匹配
3. F,E 個數不匹配 (用Fnum記錄,輸入F則++,輸入E則--4. F迴圈變數名字重複 (用map<string ,int >map_times 記錄變數的次數)

注意!題目最後邊還有一句話

attention.png

全部程式碼 (快捷複製:按住滑鼠左鍵選擇你要的程式碼然後Ctrl+c)
#include <bits/stdc++.h>
using namespace std;
int T,l,on,Fnum,Maxon;//T程式數,l程式行數,on當前複雜度,Fnum當前程式F-E個數,Maxon當前程式最大複雜度 
string On,Ans;//輸入,答案 
bool isError;//當前程式錯誤開關 
const int INF=-99999;
const bool DEBUG=false; 

bool checkRight(int w){// 檢查複雜度是否一置 
	if(w==0&&On=="O(1)") return 1; 
	else {
		int r=0;
		for(int i=4;i<On.length()-1;i++) {//這個for實際上就是從(string)On裡取出數字來 
			r*=10;
			r+=On[i]-'0';
		}
		return r==w;
	}
}
int getOn(string x,string y){//獲取複雜度 
	if(x!="n"&&y=="n") return 1;//從 常數 到 n :複雜度為 1 
	if(x=="n"&&y!="n") return INF;//從 n到常數 :由於無法進入故認為其複雜度為負無窮  依據題目 -99999 已經足夠 
	if(x=="n"&&y=="n") return 0;//從 n到 n相當於 常數 到 常數 複雜度為 0 
	
	//x y 均為數字 ,則只要判斷迴圈能否進入即可,能進入 複雜度為0 , 不能進入 複雜度則為 負無窮(INF) 
	int xx=0,yy=0;
	for(int i=0;i<x.length();i++){
		xx*=10;
		xx+=x[i]-'0';
	}
	for(int i=0;i<y.length();i++){
		yy*=10;
		yy+=y[i]-'0';
	}
	return yy>=xx?0:INF;//yy>=xx 常數級別  
}
int main(){
//	freopen("complexity.in","r",stdin);
//	freopen("complexity.out","w",stdout);
	scanf("%d",&T);
	while(T--){//小明寫了n個 A++ 程式 
		map<string ,int >map_times;//關聯容器 將變數名與次數關聯,可與下邊胡map_on合併 
		map<string ,int >map_on;//儲存變數的複雜度 
		stack<string >st;// top() 表示當前所在胡迴圈 
		isError=false;//錯誤開關關閉 
		Fnum=Maxon=on=0;//F-E記次清零 最大複雜度,當前複雜的清零 
		cin>>l>>On;//獲取每個程式初始資料 行數與期望複雜度 
		if(l%2) isError=true;//如果程式行數是個奇數,那麼F-E一定無法一一對應 
		string F,i,x,y;//準備 迴圈輸入資料 
		while(l--){
			cin>>F;
			if(F=="F"){//首字母檢測 F則開始迴圈 E則退出 
				Fnum++;//F 計次 
				cin>>i>>x>>y;
				map_times[i]++;//當前程式中的 迴圈i的 次數 
				st.push(i);//將 i迴圈 壓棧 ,棧頂 i即為當前所在迴圈  
				if(isError||map_times[i]>1){//如果已經error了,則沒必要計算複雜度 
					isError=true;//如果迴圈i再次出現 >>ERR 
					continue;
				}
				map_on[i]=getOn(x,y);//獲取 迴圈i 的複雜度 
				on+=map_on[i];//複雜度累加前邊巢狀的 
				Maxon=max(on,Maxon);//最大值迭代 
			}else{//首字母為 E 則終止 st.top() 表示的迴圈 
				if(isError)continue;// error 跳過 
				Fnum--;//F 個數 減1 
				if(st.empty())continue;//棧為空 當前執行已不在任何迴圈內 
				on-=map_on[st.top()];//當前複雜度 減去 當前迴圈st.top() 的複雜度 
				map_times[st.top()]--;//迴圈i出現的次數 減1 
				st.pop();//當前迴圈 退棧 
			}
		}
		if(Fnum!=0||isError){//F-E個數不相等 , ERR輸出 
			cout<<"ERR"<<endl;
			continue;
		}
		Ans=checkRight(Maxon)?"Yes":"No";//檢查答案是否正確 
		cout<<Ans<<endl;//輸出答案 
	}
	return 0;
}

最後更新 2018.10.30 19:01 By HaoDaDaDa