# **APCS 題解** 作者: Hank Hu # #前言(~~作者的一些廢話~~) 紀錄於2022/4/24 這是我第一次使用HackMD,做的不好看還請見諒:3。 動機是突然想把一些APCS的題目做紀錄下來。方便自己複習,也可以提供給別人學習,寫扣的算是興趣吧。雖然我也很爛QAQ。希望對程式有興趣的人能繼續奮鬥下去。因此我把這份題解寫成類似引導的方式,希望讀到這份的人可以不要只是抄Code,而是認真理解演算法和搞懂題目。 順帶一提,會不定期更新。 還有有些內容沒更新上去的部分我會盡量快速完成,還請多多包容。ex:連結沒辦法點...。 更: 紀錄於2022/9/2,因學測緣故,沒那麼常寫code,有需要更多的題解或催更可以mail我,我會盡力的TAT。 hu.0106.text@gmail.com :::warning 值得一提的是 : Zerojudge測資蠻弱的,所以有些Code可能沒那麼好,但在ZJ上過了。請見諒。習慣一些演算法使用1 base記得看細節。 ::: :::danger 建議在main函式中加上這行增加執行速度: **ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);** 若加上則切忌不可以和printf和scanf混用。 題目沒有照難易度排序。有些觀念會重複提到。 建議先學習一些運算子符號ex: $"?:"$, $"<<"$ , $">>"$...。 還有了解時間複雜度基本觀念、分析。 ::: 話不多說,開始吧! # ZJ f581 圓環出口 ## 1.題目敘述: https://zerojudge.tw/ShowProblem?problemid=f581 ## 2.解題想法: 1. 爆搜: 每次做任務都一個一個跑下去。但一定會吃TLE :(。 2. 優化: 只用找到每次做完任務停留的點就好,若終點為x,現在為now。 表示x-now所有經過的點數總和>=任務所需點數。 #### 問題(1):如何找到區間x-now的和。 :::success :::spoiler Hint \ 前綴和。 [ 外部連結(英文)](https://darrenyao.com/usacobook/cpp.pdf#page=60) 第60頁,哭啊有時候點連結會跑掉。 ::: #### 問題(2):那如果需要重頭走呢? 如何解決圓環的問題? :::success :::spoiler Hint \ 開兩倍長度的array。(索引值)1~2n 為什麼可以這樣做呢? 因為題目提到說:點數總和不超過任務所需點數。 做完如果位置>n再回到位置-n的地方。 ::: #### 問題(3):那我要怎麼知道哪個地方要當終點呢? ::: success ::: spoiler Hint \ 二分搜。 範圍(now~now+n) :::warning 注意範例程式碼的寫法下界需-1。 ::: ## 3.程式碼範例 ::: spoiler **點擊展開Code** ```cpp= #include<bits/stdc++.h> #define FOR(i,a,b) for(int i = a;i<=b;i++) using namespace std; int dp[400010]; int p[400010]; int now = 1; void BS(int pos, int l, int r , int v) { while(l!=r-1) { int m = (l+r)/2; if(p[m]-p[pos-1]<v) l = m; else r = m; } now = r; } void solve() { int n,m;cin >> n >> m; FOR(i,1,n) { cin >> dp[i]; dp[n+i] = dp[i]; } FOR(i,1,2*n+1) { p[i] = p[i-1] + dp[i]; } vector<int> v(m); FOR(i,0,m-1) cin >> v[i]; for(int it : v) { BS(now,now-1,now+n,it); now++; if(now>n) now-=n; } cout << now-1 << '\n'; } signed main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve(); } ``` ::: # ZJ e289 美麗的彩帶 ## 1.題目敘述: https://zerojudge.tw/ShowProblem?problemid=e289 ## 2.解題想法: 1. 爆搜: 枚舉每個長度為m的區間,然後每次都掃過一遍看是不是有m種顏色。時間複雜度為$O(n^2)$,一定會吃TLE :(。 2. 優化: 假設我現在在$\ [l,m]$區間,下一次要檢查$\ [l+1,m+1]$區間,那我需要變更的只有,把左界l改成l+1,右界m改成m+1,意旨刪除$arr[l]$ , $arr[r]$,且更新$arr[l+1]$, $arr[r+1]$即可。 可以思考一下要如何做到存區間顏色數和更新左右界線。 ::: success ::: spoiler Hint \ Two Pointer && Map(連結是英文的) Two Pointer([外部連結](https://darrenyao.com/usacobook/cpp.pdf#page=71)) Map([外部連結](https://darrenyao.com/usacobook/cpp.pdf#page=18)) ::: ::: info ::: spoiler Solution 維護l,r,每次更新把arr[r]位置的值加到map如果map[arr[r]] == 1就顏色種類+1,當達到l ~ r區間長度為m時,把原本arr[l]的顏色刪掉,再加上arr[l+1]的顏色,假設map[arr[l]] == 0代表顏色種類-1,而arr[l+1]在做r的時候已經加過了記得不要重複加到。 :::warning 注意題目的顏色值域A~i~是到10^150^,要開string不然會超出long long int 上限。 ::: ## 3.程式碼範例 ::: spoiler **點擊展開Code** ``` cpp= #include<bits/stdc++.h> #define FOR(j,a,b) for(int j = a;j<=b;j++) using namespace std; void solve() { int n,m;cin >> m >> n; vector<string> v(n); FOR(i,0,n-1) cin >> v[i]; map<string,int> cnt; int l = 0,r=0,type=0,ans=0; while(l<n&&r<n) { if(r-l==m) { cnt[v[l]]--; if(cnt[v[l]]==0) type--; l++; } cnt[v[r]]++; if(cnt[v[r]] == 1) type++; r++; if(type==m) ans++; } cout << ans << '\n'; } signed main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve(); } ``` ::: # ZJ b967 血緣關係 ## 0.前置觀念: Tree Algorithm && DFS [外部連結](https://www.youtube.com/watch?v=XEzd5yZLpoM&ab_channel=sprout-tw) 通常用E表示點edge 用V表示邊vertices ## 1.題目敘述: https://zerojudge.tw/ShowProblem?problemid=b967 ## 2.解題想法: 1. 爆搜: 對每個點DFS找最長距離。時間複雜度為$O((V+E)^2)$,一定吃TLE : (。 2. 優化: 會觀察到若A可以到B, B可以到C, A->C最遠距離會等於A->B的最遠距離+B->C的最遠距離。可以想想如何找到B? 註:這題是裸題所以沒有很多觀念,可以Google搜尋樹上最遠距離或是樹直徑。 :::success ::: spoiler Hint 1 \ B為樹根,且樹有一個特性,任意點都可以當根。意旨拉起任意點,都會形成一棵樹。 ::: ::: info :::spoiler Solution 1 \ 樹DP,對節點找子樹最遠點和子樹第二遠點,然後一值併到根。然後輸出兩點距離即可。 ::: :::success ::: spoiler Hint 2 \ 先找左邊或右邊最遠,再找對那個點來說最遠的距離。 ::: :::info :::spoiler Solution 2 \ 樹上DFS 2次,第一次找最深的點x,第二次找以x為根的最深點。 ::: ## 3.程式碼範例 ### (1) 樹DP版: ::: spoiler **點擊展開Code** ```cpp= #include<bits/stdc++.h> #define ll long long #define pb push_back #define vi vector<int> #define FOR(i,a,b) for(int i = a;i<=b;i++) using namespace std; const int N = 1e5+10; vi e[N]; int d_max[N],d_sec[N],ans = 0; void dfs(int u, int par) { for(int chi:e[u]) { if(chi!=par) { dfs(chi,u); int now = d_max[chi]+1; if(now>d_max[u]) { d_sec[u] = d_max[u]; d_max[u] = now; } else if(now>d_sec[u]){ d_sec[u] = now; } } } ans = max(ans,d_max[u]+d_sec[u]); } void solve(int n) { FOR(i,1,n-1) { int par,chi;cin >> par >> chi; e[par].pb(chi); e[chi].pb(par); } dfs(0,0); cout << ans << '\n'; FOR(i,0,n) { while(e[i].size()) e[i].clear(); d_max[i] = 0; d_sec[i] = 0; ans = 0; } } signed main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); int n; while(cin >> n) solve(n); } ``` ::: ### (2) 兩次DFS版: ::: spoiler **點擊展開Code** ```cpp= #include<bits/stdc++.h> #define FOR(j,a,b) for(int j = a;j<=b;j++) #define pb push_back using namespace std; const int N = 1e5+10; vector<int> e[N]; int subtree[N],root = 0; void dfs(int u, int par) { for(int chi:e[u]) { if(chi!=par) { subtree[chi] = subtree[u]+1; root = (subtree[root]>subtree[chi])?root:chi; //上面那行看不懂可以google"?:"運算子 dfs(chi,u); } } } void solve(int n) { FOR(i,1,n-1) { int a,b;cin >> a >> b; e[a].pb(b); e[b].pb(a); } dfs(0,0); FOR(i,0,n) subtree[i] = 0; dfs(root,root); cout << subtree[root] << '\n'; // initial FOR(i,0,n-1) { while(!e[i].empty()) e[i].clear(); subtree[i] = 0; } root = 0; } signed main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); int n; while(cin>>n) solve(n); } ``` ::: # ZJ c575 基地台 ## 1.題目敘述: https://zerojudge.tw/ShowProblem?problemid=c575 ## 2.解題想法: 1. 爆搜: 直徑從1開始,依序檢查每個點蓋基地台有沒有蓋到全部範圍。一定吃TLE :(。 2. 優化: 如果直徑R不行,那代表R-1一定不行,如果直徑R可以,那R+1可不可以? 答案是一定可以。所以只要找到最一開始的R,如果不行就找更大的R,可以就找更小的R。 ### 問題(1): 最一開始的R要選多少。 :::success :::spoiler Hint \ **二分搜。** R最小是1,最大是基地台最大的位置-最小的位置。且因為R可以放K個代表R+1也可以放<=K個,R不行放K個,那代表R-1一定不行放K個。R對K具有單調性,可以二分搜(R小則K大,R大則K小)。 :::warning 注意範例程式碼的寫法下界需-1。 ::: ### 問題(2): 如何知道要放多少個基地台。 :::success ::: spoiler Hint \ **Greedy**(?) 先從最小開始放(R/2的位置),如果現在位置>上一個放的+R,更新現在位置。代表多放一個基地台。 ::: warning 註:now的位置一定要+k<第一個城市位置不然無法更新。 ::: ## 3.程式碼範例 ::: spoiler **點擊展開Code** ```cpp= #include<bits/stdc++.h> #define FOR(j,a,b) for(int j = a;j<=b;j++) using namespace std; vector<int> v; int n,k; bool check(int m) { int now = -1e9, put = 0; FOR(i,0,n-1) { if(now+m>=v[i]) continue; now = v[i]; put++; } return put<=k; } int Binary_Search(int l, int r) { while(l!=r-1) { int m = (l+r)/2; if(check(m)) r = m; else l = m; } return r; } void solve() { cin >> n >> k; v.resize(n); FOR(i,0,n-1) cin >> v[i]; sort(v.begin(),v.end()); int l = 1; int r = v[n-1] - v[0]; cout << Binary_Search(l-1,r) << '\n'; } signed main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve(); } ``` ::: # ZJ f315 低地距離 ## 0.前置觀念: BIT or called Fenwick Tree|| Segment Tree 當初看YT和自學的,外部連結能給的很少 QWQ。 建議先理解觀念,有些程式碼不是那麼乾淨參考就好。還有BIT要查蠻多資料才能知道怎麼寫的喔。 [推一個wiwiho的www](https://hackmd.io/@wiwiho/cp-note/%2F%40wiwiho%2FCPN-binary-indexed-tree) [BIT](https://www.youtube.com/watch?v=CWDQJGaN1gY&ab_channel=TusharRoy-CodingMadeSimple) [線段樹](https://www.youtube.com/watch?v=e_bK-dgPvfM) (雖然中國的用語有差異但至少是中文owo) PS:線段樹也可以去看資訊之芽。 [線段樹(一)](https://www.youtube.com/watch?v=GLuT4zKzdjc&ab_channel=sprout-tw) [線段樹(二)](https://www.youtube.com/watch?v=5DAIKf61xLs&ab_channel=sprout-tw) ## 1.題目敘述: https://zerojudge.tw/ShowProblem?problemid=f315 ## 2.解題想法: 1. 爆搜: 找到每個數值的區間然後跑一遍,看區間內有多少比它小的數值,但必定吃TLE : (。 2. 優化: 關鍵是我需要找到的只有在這段$[l,r]$區間比我小的值而已,所以不如初始一個數值都為0的陣列,從最小的值開始找到最大的值,然後每次找完把當前的位置更新上去。意旨當前位置的陣列值+1($arr[l]+=1$ , $arr[r]+=1$),然後就會變成查詢區間和。 **問題(1): 我要怎麼從最小的開始放,Sort嗎?** ::: success ::: spoiler Hint \ 因為值為$1$~$n$所以可以開n長度的pair陣列,first紀錄左邊的位置,second紀錄右邊的位置。 ::: **問題(2): 查詢區間何要怎麼做呢? 用前面提到的前綴和沒辦法線性時間更新值阿,還有更好的做法嗎。** ::: success ::: spoiler Hint 這時候就該線段樹or BIT出場了!,可以做到$O(logN)$時間的查詢和更新值。前綴和查詢是$O(1)$,但更新值是$O(n)$,會TLE所以步行用。先看完前置觀念再去解題吧!。 ::: ::: warning 注意要開long long int因為答案會超出int上限。 ::: :::danger ::: spoiler 一些細節 \ **BIT和線段樹各有優缺點,但通常這種同時可以用BIT和線段樹的題目,個人習慣寫BIT。因為寫BIT比刻一顆線段樹簡單很多,所需時間也少,我刻一顆線段樹大概要15 ~ 20分鐘,但是BIT只用2 ~ 3分鐘而已XD。BIT空間複雜度也比線段樹小(線段樹要開4倍)。比較不會因為題目數值範圍被卡到陣列長度uwu。** ::: ## 3.程式碼範例 ### (1)線段樹版本 ![](https://i.imgur.com/OD9T8Zq.png) ::: spoiler **點擊展開Code** ```cpp= #include<bits/stdc++.h> #define int long long #define FOR(j,a,b) for(int j = a;j<=b;j++) #define F first #define S second using namespace std; const int N = 2e5+10; struct segment_tree{ int t[4*N]; int que(int ql, int qr, int l, int r, int n) { if(ql<=l&&r<=qr) { return t[n]; } int m = (l+r)/2; if(qr<=m) return que(ql,qr,l,m,n*2); else if(ql>m) return que(ql,qr,m+1,r,n*2+1); else return que(ql,qr,l,m,n*2) + que(ql,qr,m+1,r,n*2+1); } void upd(int p, int v, int l, int r, int n) { if(l==r) { t[n] = v; return; } int m = (l+r)/2; if(p<=m) upd(p,v,l,m,n*2); else upd(p,v,m+1,r,n*2+1); t[n] = t[n*2] + t[n*2+1]; } }seg_tree; void solve() { int n, ans = 0;cin >> n; pair<int,int> arr[n+2]; FOR(i,1,2*n) { int a;cin >> a; if(arr[a].F==0) arr[a].F=i; else arr[a].S=i; } FOR(i,1,n) { int sum = seg_tree.que(arr[i].F,arr[i].S,1,2*n,1); ans += sum; seg_tree.upd(arr[i].F,1,1,2*n,1); seg_tree.upd(arr[i].S,1,1,2*n,1); } cout << ans << '\n'; } signed main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve(); } ``` ::: ### (2)BIT版本 ![](https://i.imgur.com/E8qfKUo.png) ::: spoiler **點擊展開Code** ```cpp= #include<bits/stdc++.h> #define FOR(j,a,b) for(int j = a;j<=b;j++) #define int long long #define F first #define S second using namespace std; const int N = 1e5+10; pair<int,int> arr[N]; struct bit{ int t[2*N]; void upd(int p, int v) { for(;p<=2*N;p+=(p&-p)) t[p]+=v; } int query(int p) { int res = 0; for(;p>0;p-=(p&-p)) res+=t[p]; return res; } }bit; void solve() { int n, ans = 0;cin >> n; FOR(i,1,2*n) { int a;cin >> a; if(arr[a].F==0) arr[a].F=i; else arr[a].S=i; } FOR(i,1,n) { int sum = bit.query(arr[i].S) - bit.query(arr[i].F-1); ans += sum; bit.upd(arr[i].F,1); bit.upd(arr[i].S,1); } cout << ans << '\n'; } signed main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve(); } ``` ::: # ZJ h084 牆上海報 ## 1.題目敘述: https://zerojudge.tw/ShowProblem?problemid=h084 ## 2.解題想法: 1.觀察題目: 會發現可以貼海報的高度最高就是所有牆的裡最高的,最小則是1。所以我可以直接爆搜嗎? 答案顯然是否。因為時間複雜度會到$O(n·h_i)$ 2.優化: 想一下,假設我現在檢查的高度是$H$,如果$H$不行,那$H-1$可以嗎?,$H+1$可以嗎?答案是$H+1$肯定不行,而$H-1$可能可以。那如果$H$可以,那$H-1$可以嗎?,$H+1$可以嗎?答案是$H+1$可能可以,而$H-1$肯定可以。各位可以思考一下為什麼。 **問題(1): 如何有效枚舉H高度。** :::success ::: spoiler Hint \ 有單調性的東西,最好用的當然是二分搜! ::: **問題(2): 如何檢查這高度能不能放。** :::success :::spoiler Hint \ 直接檢查就好了,看每段>=H的區間長,然後放到不能放海報為止。時間複雜度是$O(N)$,乘上二分搜時間複雜度$O(logH_i)$,總時間複雜度是$O(NlogH_i)$。並不會TLE。 ::: ::: danger ::: spoiler 注意事項,ZJ卡90%再來看www。 \ 這題二分搜上界要+1,會跟前面二分搜的題不一樣,算是h對k函數的小特性(?,不然答案是r的時候會出錯,輸出r-1,但在Zerojudge上會90%,測資是真的蠻弱的www。 ::: ## 3.程式碼範例 ::: spoiler **點擊展開Code** ```cpp= #include<bits/stdc++.h> #define vi vector<int> #define all(p) p.begin(),p.end() #define FOR(j,a,b) for(int j = a;j<=b;j++) using namespace std; const int N = 4e5+10; vi v; vi post; int n,k; bool check(int m) { int put = 0,now = 0; for(int i:v) { if(i<m) { now = 0; continue; } now++; while(put<k&&now>=post[put]) { now-=post[put]; put++; } } return put<k; } int BS(int l, int r) { while(l!=r-1) { int m = (l+r)/2; if(check(m)) r = m; else l = m; } return l; } void solve() { int mxh = 0; cin >> n >> k; v.resize(n); post.resize(k); FOR(i,0,n-1) cin >> v[i],mxh = max(mxh,v[i]); FOR(i,0,k-1) cin >> post[i]; cout << BS(1,mxh+1) << '\n'; } signed main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve(); } ``` ::: # ZJ g277 幸運數字 ## 1.題目敘述: https://zerojudge.tw/ShowProblem?problemid=g277 ## 2.解題想法: 1.觀察: 很明顯這題就是,給定一個陣列,然後要你找區間最小值和區間和。那我們分成2部分思考。 問題(1): 如何查找區間和。 :::success ::: spoiler Hint 可以用到前面提過的前綴和的概念! ::: 問題(2): 如何查找區間最小值。 :::success ::: spoiler Hint \ 因為不會更新新的值上去,所以照小到大sort就好了! 或是也可以用一些RMQ演算法維護。 ::: ::: warning ::: spoiler 一些細節。 \ 記得開long long int因為前綴和可能會超出int上限! ::: ## 3.程式碼範例 ::: danger ::: spoiler 一些小提醒。 \ 這題重點就是**不帶修改**的查詢區間和和區間最小值。所以不要想說一定要用難的演算法。如果你不想用pair存位置,或是用線段樹寫這題。因為值域到$10^7$所以可以嘗試開一個長度$10^7$的陣列然後存每個值的位置,或是用map。用map的話時間會稍稍慢一點,但空間會小很多。 上面是Map,下面是$10^7$陣列。![](https://i.imgur.com/x2GRrrK.png) ::: ### **(1) Sort+前綴和版** ::: spoiler **點擊展開Code** ```cpp= #include<bits/stdc++.h> #define all(p) p.begin(),p.end() #define FOR(j,a,b) for(int j = a;j<=b;j++) #define int long long #define F first #define S second using namespace std; const int N = 3e5+2; int pre[N]; void solve() { int n;cin >> n; vector<int> org(n+1); vector<pair<int,int>> v(n+1); FOR(i,1,n) { cin >> v[i].F; v[i].S = i; org[i] = v[i].F; } FOR(i,1,n) pre[i] = pre[i-1] + v[i].F; sort(all(v)); int l = 1,r = n; FOR(i,1,n) { if(l<=v[i].S&&v[i].S<=r) { if(pre[r]-pre[v[i].S]<pre[v[i].S-1]-pre[l-1]) r = v[i].S-1; else l = v[i].S+1; } } cout << org[r] << '\n'; } signed main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve(); } ``` ::: ### **(2) 線段樹版** 建議真的想不到再刻qq。 ::: spoiler **點擊展開Code** ```cpp= #include<bits/stdc++.h> #define all(p) p.begin(),p.end() #define FOR(j,a,b) for(int j = a;j<=b;j++) #define int long long #define F first #define S second using namespace std; const int N = 3e5+10; struct SegmentTree { int arr[N], t[4*N], s[4*N]; void build_tree(int l, int r, int n) { if(l==r) { t[n] = arr[l]; s[n] = arr[l]; return; } int m = (l+r)/2; build_tree(l,m,2*n); build_tree(m+1,r,2*n+1); t[n] = t[2*n] + t[2*n+1]; s[n] = min(s[2*n],s[2*n+1]); } int que_sum(int ql, int qr ,int l ,int r ,int n) { if(qr<ql) return 0; if(ql<=l&&r<=qr) return t[n]; int m = (l+r)/2; if(qr<=m) return que_sum(ql,qr,l,m,2*n); else if(ql>m) return que_sum(ql,qr,m+1,r,(2*n)+1); else return que_sum(ql,qr,l,m,2*n) + que_sum(ql,qr,m+1,r,(2*n)+1); } int que_min(int ql, int qr ,int l ,int r ,int n) { if(ql<=l&&r<=qr) return s[n]; int m = (l+r)/2; if(qr<=m) return que_min(ql,qr,l,m,2*n); else if(ql>m) return que_min(ql,qr,m+1,r,(2*n)+1); else return min(que_min(ql,qr,l,m,2*n),que_min(ql,qr,m+1,r,(2*n)+1)); } }seg; void solve() { int n;cin >> n; map<int,int> pos; FOR(i,1,n) { int x;cin >> x; seg.arr[i] = x; pos[x] = i; } seg.build_tree(1,n,1); int l =1,r = n; while(l!=r) { int v = seg.que_min(l,r,1,n,1); int p = pos[v]; int l_sum = seg.que_sum(l,p-1,1,n,1); int r_sum = seg.que_sum(p+1,r,1,n,1); if(l_sum>r_sum) r = p-1; else l = p+1; } cout << seg.arr[l] << '\n'; } signed main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve(); } ``` ::: ### (3) 笛卡兒樹$O(n)$作法 (作者真的不會寫QQ) # ZJ f608 飛黃騰達 ## 0.前置觀念: [LIS](https://web.ntnu.edu.tw/~algo/Subsequence.html#1) ## 1.題目敘述: https://zerojudge.tw/ShowProblem?problemid=f608 ## 2.解題想法: **1.觀察性質:** 只能往右和上走,代表下次取到的珠子,x座標和y座標一定大於等於現在的x,y座標。然後能取越多越好。有沒有覺得這跟甚麼演算法很像。 :::success :::spoiler Hint \ 沒錯就是非嚴格遞增的LIS(longest increasing subsequence) ::: ::: info ::: spoiler Solution \ 這時候你可能會問,可是題目有2個維度ㄟ?-?,那就先sort過其中一個座標,然後就可以變單維度的LIS了阿。先對x座標或y座標做sort都可以得出最佳解,這跟題目給的非嚴格遞增的性質有關,各位可以想想看為什麼。 然後這題有兩種做法,一種是開一個陣列紀錄最長LIS。另一種是用BIT維護目前數字可以接在多少數字後面。 **需要注意的是如果用第一種方法,請注意第18行,判斷是否為空的條件需要在判斷是否大於back數字的條件的前面。否則會因為vector為空出現RE。因為是非嚴格遞增,所以要用upper_bound,如果用BIT,則query要查訊自己位置而不是位置-1。** ::: ## 3.程式碼範例 ### (1) LIS ::: spoiler **點擊展開Code** ![](https://i.imgur.com/WM4yRmC.png) ```cpp= #include<bits/stdc++.h> #define FOR(i,a,b) for(int i = a;i<=b;i++) #define pb push_back #define F first #define S second #define all(x) (x).begin(),(x).end() using namespace std; void solve() { int n;cin >> n; vector<pair<int,int>> v; FOR(i,0,n-1) { int x,y;cin >> x >> y; v.pb({x,y}); } sort(all(v)); vector<int> lis; FOR(i,0,n-1) { if(lis.empty()||v[i].S>=lis.back()) lis.pb(v[i].S); else *upper_bound(all(lis),v[i].S) = v[i].S; } cout << lis.size() << '\n'; } int main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve(); } ``` ::: ### (2) LIS(BIT) 待更 # ZJ h029(f163) 貨物分配 ## 1.題目敘述: https://zerojudge.tw/ShowProblem?problemid=h029 https://zerojudge.tw/ShowProblem?problemid=f163 (測資較強) ## 2.解題想法: ## 3.程式碼範例 ::: spoiler **點擊展開Code** ```cpp= #include<bits/stdc++.h> #define FOR(i,a,b) for(int i = a;i<=b;i++) using namespace std; const int N = 1e6+10; int w[2*N],right_node[2*N],left_node[2*N],output = 0; void build(int now) { if(left_node[now]!=0) build(left_node[now]); if(right_node[now]!=0) build(right_node[now]); w[now] += w[left_node[now]] + w[right_node[now]]; } void put(int now ,int value) { output = now; if(left_node[now]!=0&&w[left_node[now]]<=w[right_node[now]]) put(left_node[now],value); else if(right_node[now]!=0) put(right_node[now],value); w[now] += value; } void solve() { int n,m;cin >> n >> m; vector<int> goods(m); FOR(i,n,2*n-1) cin >> w[i]; FOR(i,0,m-1) cin >> goods[i]; FOR(i,1,n-1) { int now,l,r;cin >> now >> l >> r; right_node[now] = r; left_node[now] = l; } build(1); // 1 base FOR(i,0,m-1) { put(1,goods[i]); cout << output << ' '; } cout << '\n'; } int main() { ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve(); } ``` :::