# 時間複雜度概述 ## - ---- Q:什麼是時間複雜度? A:描述程式執行時間的函式 ---- $O(f(n)), o(g(n)), Θ(h(n)), Ω(i(n))$ 常使用$O(f(n))$表示,唸作Big-O,其他先不提 ---- Define $O(f(n))$: 當資料量為$n$時, 程式執行時間不超過$f(n)$的常數倍 ---- $If\ g(x)=O(f(x)),∃ M,x_0>0,$ $such\ that\ ∀x≥ x_0,then\ |g(x)|≤M|f(x)|$ ---- 計算原則 * 每種操作(加減乘除模、賦值、條件判斷...)都當成一單位時間 * 常數倍數不計 * 如果將程式分成兩段複雜度,總複雜度取較大的那段(低次項不計) * 若$O(f(n))$的程式執行$g(n)$次,總複雜度為兩者相乘,即$O(f(n)*g(n))$ ---- 試估算以下程式碼的時間複雜度 ```cpp int n,i=0,j=0; cin >> n; while(i<n){ while(j<i){ j++; } i++; } ``` 總複雜度: $O(N^2)$ --- ### APCS出題模式 前兩題通常不需考慮複雜度, 後兩題需要估算複雜度。 ---- | n | 過萬 | 數千 | 數百 | 20~25 | 10 | | -------- | -------- | -------- | -------- | -------- | -------- | | 複雜度 | $O(n)$ or $O(nlog(n))$ | $O(n^2)$ | $O(n^3)$ | $O(2^n)$ | $O(n!)$ | ---- Judge平均速度為$10^8$, 只要將範圍帶入估計複雜度除以$10^8$ 即可預測執行時間, 判斷是否會超時(TLE) --- 例題一 ---- Q: 給 $t$ 個正整數 $x_i$,判斷 $n$ 是否為質數。 其中 $1\le t \le 100,\ 1 \le x_i \le 10^{10}$ ---- 試除法 Solution 總複雜度 $O(T\sqrt {x_{max}})$ ```cpp= #include<iostream> #include<cmath> using namespace std; int main(){ int t; cin >> t; while(t--){ long long x; cin >> x; if(x==1){ cout << "No\n"; continue; } long long s = sqrt(x+0.5); bool chk = true; for(int i=2;i<=s;i++) if(x%i==0) { chk = false; break; } if(chk) cout << "Yes\n"; else cout << "No\n"; } return 0; } ``` --- 例題二 ---- Q: 給 $t$ 個正整數 $x_i$,判斷 $n$ 是否為質數。 其中 $1\le t \le 10^6,\ 1 \le x_i \le 10^{6}$ ---- 突破口:$N$比較小,知道$1 \sim 10^6$是不是質數就好! ---- 篩法 ----- ~~1~~ 2 3 ~~4~~ 5 ~~6~~ 7 ~~8~~ ~~9~~ ~~10~~ 11 ~~12~~ 13 ~~14~~ ~~15~~ ~~16~~ 17 ~~18~~ 19 ~~20~~ 複雜度:$\frac{N}{2} + \frac{N}{3} + \frac{N}{5} + ...$ ---- 引理: $1+\frac{1}{2}+\frac{1}{3}+...+\frac{1}{N} = O(log(N))$ $\to$篩法複雜度取上界 $\frac{N}{2} + \frac{N}{3} + \frac{N}{5} + ... \le N(1+\frac{1}{2}+\frac{1}{3}+...+\frac{1}{N})=O(NlogN)$ 作法:用$O(KlogK)$建質數表,以$O(1)$回答詢問 總複雜度:$O(KlogK+t), K=10^6,t\le10^6$ ---- Solution ```cpp= #include<iostream> using namespace std; const int K = 1e6+2;//1000002.0 int isPrime[K]; int main(){ for(int i=2;i<=K;i++) isPrime[i]=1; for(int i=2;i<=K;i++){ if(isPrime[i]==1){ for(int j=i*2;j<=K;j+=i) isPrime[j]=0; } } int T,x; cin >> T; while(T--){ cin >> x; cout << (isPrime[x]?"Yes\n":"No\n"); } } ``` --- 例題三 ---- Q: 給你一個只包含小寫字母的字串$S$, 請找出最短子字串包含所有小寫字母, 如果有多個子字串,請輸出第一次出現的, 如果不存在這種子字串則輸出NA。 其中$| S | \le 10^6$ ---- 直覺: 枚舉子字串左界$L$($O(n)$),向右一直延伸右界$R$直到區間包含所有小寫字母為止($O(n)$), 總複雜度$O(n^2)$,太大! ---- 優化: $L$向右一格後將原本$L$的字母次數減一,$R$就能直接從原本的地方繼續掃,複雜度變成$O(n)$ ---- Solution ```cpp= #include<iostream> #include<cstring> using namespace std; int frq[202],now; int main(){ string S; cin >> S; int N = S.length(),L=-1,minLen=1e7; for(int l=0,r=0;l<N;l++){ while(now<26&&r<N){ char c = S[r++]; if(!frq[c) now++; frq[c]++; } if(now==26&&minLen>r-l){ minLen = r-l; L=l; } if(l<r){ char c = S[l]; frq[c]--; if(!frq[c]) now--; } } cout << (L==-1?"Na":S.substr(L,minLen)) << endl; } ```
{"metaMigratedAt":"2023-06-16T23:25:44.807Z","metaMigratedFrom":"YAML","title":"時間複雜度概述","breaks":true,"contributors":"[{\"id\":\"ec85c8ff-a359-4b91-ac52-120dad52b91f\",\"add\":3588,\"del\":275}]"}
    159 views