CPE數學系
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    {%hackmd @hipp0\Hippotumuxthem %} <style> .text-center{ text-align: center; //文字置中 } .text-left{ text-align: left; //文字靠左 } .text-right{ text-align: right; //文字靠右 } </style> <style> .reveal .slides { text-align: left; font-size:30px; } </style> ### 分治法內容 - 分治法介紹 - 複雜度 - 費式數列 - 結論 --- ### 分治法 (Divide and Conquer) 分治是一種非常重要的演算法思維模式與策略,有很多重要的演算法都是根據分治的思維模式,例如快速排序法、合併排序法、快速傅立葉轉換(FFT)、矩陣乘法、整數乘法以及在一些在計算幾何的知名演算法都是分治的策略。 當碰到一個不曾見過的問題,分治往往是第一個思考的重點,因為分治的架構讓我們很容易找到比暴力要好的方法。 ---- ## 核心想法 分治法主要有三個步驟,切割,解決,合併。 - 切割:把問題以「相同問題」切割成更多小的子問題。 - 解決:把切割完的子問題解決。 - 合併:把解決完的子問題合併起來成為問題的答案。 ---- ### 代碼 ```cpp= // 看問題需要回傳什麼型態 int Divide_and_Conquer(int left, int right) { // 要多小才不繼續分 if (right - left < 1) { // 回傳你小問題的答案 return 1; } int mid = (left + right)/2; // 切割問題 int left_return = Divide_and_Conquer(left, mid); int right_return = Divide_and_Conquer(mid+1, right); // 合併問題 (假設回傳相加) int total_return = left_return + right_return; return total_return; } ``` ---- ### 簡單例子1 (找序列最大/小值) 題目 : 給定長度為 N 的序列,找到最大值/最小值 ---- ### 想法 按照分治法三個步驟,切割,解決,合併,把問題大化小。 - 切割: 分成左右兩半 (切到剩下兩個以下就停止) - 解決: 計算其最大值/最小值 - 合併: 根據左右兩半回傳的值,計算其最大值最小值並回傳 ---- ### 代碼 (計算最大值演示) ```cpp= int Divide_find_max(int left, int right, vector<int> &vec) { // 最小問題 if (right - left < 2) { return max(vec[left], vec[right]); } // 切割 int mid = (left + right)/2; int left_ret = Divide_find_max(left, mid, vec); int right_ret = Divide_find_max(mid+1, right, vec); int total_ret = max(left_ret, right_ret); return total_ret; } ``` ---- ### 簡單例子2 (合併排序法) 在排序那篇中,有提到合併排序法,合併排序法的根本原理就是分治法 ---- ### 想法 按照分治法三個步驟,切割,解決,合併,把問題大化小。 - 切割: 分成左右兩半 (切到剩下兩個以下就停止) - 解決: 回傳排序好的序列 ---- ### 合併: 合併的時候,會拿到左右兩區間 "排序好的序列" ,這邊可以利用排序好的特性來進行合併。 假設目前左區間回傳 \[ 1, 3, 8, 9] 右區間回傳 \[2, 4, 6,] ---- ### 合併: \[ 1, 3, 8, 9] \[2, 4, 6] 利用已經排序的特性,左邊一定比右邊小,所以可以把兩個序列從左邊比到右邊,放進新的序列中。 ---- ### 第一輪 \[<font color=#FF0000> 1 </font>, 3, 8, 9] \[<font color=#0000FF> 2 </font>, 4, 6] #### 新序列 \[<font color=#FF0000> 1 </font>] ---- ### 第二輪 \[1, <font color=#FF0000> 3 </font>, 8, 9] \[<font color=#0000FF> 2 </font>, 4, 6] #### 新序列 \[1, <font color=#0000FF> 2 </font>] ---- ### 第三輪 \[1, <font color=#FF0000> 3 </font>, 8, 9] \[2, <font color=#0000FF> 4 </font>, 6] #### 新序列 \[1, 2, <font color=#FF0000> 3 </font>] ---- ### 第四輪 \[1, 3, <font color=#FF0000> 8 </font>, 9] \[2, <font color=#0000FF> 4 </font>, 6] #### 新序列 \[1, 2, 3, <font color=#0000FF> 4 </font>] ---- ### 第五輪 \[1, 3, <font color=#FF0000> 8 </font>, 9] \[2, 4, <font color=#0000FF> 6 </font>] #### 新序列 \[1, 2, 3, 4, <font color=#0000FF> 6 </font>] ---- ### 第六輪 \[1, 3, <font color=#FF0000> 8 </font>, 9] \[2, 4, 6] #### 新序列 \[1, 2, 3, 4, 6, <font color=#FF0000> 8 </font>] ---- ### 第七輪 \[1, 3, 8, <font color=#FF0000> 9 </font>] \[2, 4, 6] #### 新序列 \[1, 2, 3, 4, 6, 8, <font color=#FF0000> 9 </font>] --- ### 複雜度 這兩個簡單的例子可以推到 - 找最大值/最小值: $O(N)$ - 合併排序法: $O(NlogN)$ ---- ### 複雜度 如果分割的很少,那每個問題的"量"就會很多, 如果分割的很多,那每個問題的"量"雖然少,但合併次數也會很多, 到底要怎麼來計算複雜度呢? ---- ### 複雜度 分治是一個遞迴的演算法,不像迴圈的複雜度可以用加總的方法或是乘法計算,遞迴的複雜度是由遞迴關係式(recurrence relation)所表達。計算複雜度必須解遞迴關係式。遞迴關係又稱為差分方程式(difference equation),解遞迴關係是個複雜的事情。 分治演算法的常見形式是將一個大小為 n 的問題切成 a 個大小為 b 的子問題此外必須做一些分割與合併的工作。假設大小為 n 的問題的複雜度是 T(n),而分割合併需要的時間是 f(n),可以得到以下遞迴關係: $T(n) = aT(\frac{n}{b}) + f(n)$ ---- ### 主定理 (Master Method) 原理就是根據剛剛說到的,要來評估分割,合併的關係。 一個分治的問題可以寫成 $T(n) = aT(\frac{n}{b}) + f(n)$ 主定理分成三種情況 ---- ### 情況一 (簡單說,合併解決問題 < 分割) 如果存在 ε > 0 使得 $f(n) = O(n^{log_b (a) - ε})$ 則 $T(n) = Θ(n^{log_b a})$ ---- ### 情況二 (合併解決問題 = 分割) 如果存在 ε $\geq$ 0 使得 $f(n) = Θ(n^{log_b a} log^εn)$ 則 $T(n) = Θ(n^{log_b a} log^{ε+1}n)$ ---- ### 情況三 (合併解決問題 > 分割) 如果存在 ε > 0 使得 $f(n) = Ω(n^{log_b (a) + ε})$ 且存在 c < 1 滿足 $af(\frac{n}{b}) \leq cf(n)$ $T(n) = Θ(f(n))$ ---- ### 複雜度分析-找最大值/最小值 $T(n) = aT(\frac{n}{b}) + f(n)$ 根據切割和合併寫下 $T(n) = 2T(\frac{n}{2}) + Θ(1)$ 根據情況一 存在 ε > 0 使得 $f(n) = O(n^{log_2 (2) - ε})$ 所以 $T(n) = Θ(n^{log_2 2}) = Θ(n)$ ---- ### 複雜度分析-合併排序法 $T(n) = aT(\frac{n}{b}) + f(n)$ 根據切割和合併寫下 $T(n) = 2T(\frac{n}{2}) + Θ(n)$ 根據情況二 存在 ε = 0 使得 $f(n) = Θ(n^{log_2 2} log^εn)$ 則 $T(n) = Θ(n^{log_2 2} log^{ε+1}n) = Θ(nlogn)$ ---- ### 題外話 主定理並不是萬能的,還是會有很多例外,複雜一點就變得很難評估 也有另外一種算法 Recursion-Tree Method,如果有興趣的話可以自行查詢。 --- ### 費式數列 前面提過很多很多次,已經學會了最原始遞迴,後來的動態規劃,再到用線性代數的方式和矩陣快速冪求解。 費式數列是一個很酷的數列,無處不在,甚至和黃金比例也產生了關係,接下來就來利用費式數列的一些性質,導出一些式子,就可以利用分治法求解。 為了方便,定義費式數列從第一項開始,也就是 $F_1 = F_2 = 1$ ---- ### 性質一 (不會用到,但有趣提一下) $F_{n+2} = \sum_{k=1}^n F_k + 1$ ---- ### 證明 首先有 $F_{n+2} = F_{n+1} + F_{n}$ 如果展開 n + 1 項 $F_{n+2} = F_{n} + F_{n-1} + F_{n}$ 繼續展開直到 n = 2 $F_{n+2} = F_{2} + F_{1} + ... + F_{n-1} + F_{n}$ 定義得 $F_{2} = 1$ 得到 $F_{n+2} = 1 + F_{1} + ... + F_{n-1} + F_{n}$ ---- ### 性質二 第(m+n)項等於第 m 項乘上第(n-1)項後再加上第(m+1) 項乘上第 n 項之和 $F_{m+n} = F_m F_{n-1} + F_{m+1}F_{n}$ ---- ### 證明 利用 $F_1 = F_2 = 1$,以及費式數列定義得 $F_{m+n} = F_{1}F_{m+n-2}+F_{2}F_{m+n-1}$ = $F_{1}F_{m+n-2} + F_{2}(F_{m+n-2} + F_{m+n-3})$ = $F_{m+n-2}(F_1 + F_2) + F_{2}F_{m+n-3}$ = $F_{2}F_{m+n-3} + F_3F_{m+n-2}$ 會發現是不是又跟一開始長的形式一樣,所以繼續推下去可以得到 = $F_{m-1}F_{n} + F_mF_{n-1}$ ---- ### 奇偶項恆等式 - 奇數項 $F_{2n-1} = F_n^2 + F_{n-1}^2$ - 偶數項 $F_{2n} = (F_{n-1} + F_{n+1})F_n = (2F_{n-1} + F_{n})F_n$ #### 證明 由性質二 $F_{m+n} = F_m F_{n-1} + F_{m+1}F_{n}$ 奇數項令 m = n-1 直接得證 偶數項令 m = n ,然後展開 n + 1 項得證 ---- ### 分治法求解 剛剛得到 - 奇數項 $F_{2n-1} = F_n^2 + F_{n-1}^2$ - 偶數項 $F_{2n} = (F_{n-1} + F_{n+1})F_n = (2F_{n-1} + F_{n})F_n$ 所以發現可以直接根據奇數偶數分治下去 ---- ### 實作 ```cpp= const int mod = 1e9 + 7; long long fib(long long n) { // 從第一項算 if (n <= 2) return 1; if (n % 2 != 0) { long long left_ret = fib((n+1)/2 - 1); long long right_ret = fib((n+1)/2); long long total_ret = (left_ret*left_ret + right_ret*right_ret) % mod; return total_ret; } else { long long left_ret = fib(n/2 - 1); long long right_ret = fib(n/2); long long total_ret = (2*left_ret + right_ret)*right_ret % mod; return total_ret; } } ``` ---- ### 複雜度分析 很不幸的,目前複雜度還是 $O(N)$,雖然分成了一半一半,但並沒有做到任何可以加快的部分。 #### 主定理 $T(n) = 2T(\frac{n}{2}) + O(1)$ 這個跟找最小/大值一樣。 所以時間複雜度真的為 $O(N)$ ---- ### 加快 會發現雖然分成了一半,可以讓 n 快速下降,但是總數量卻是不變的,不過,雖然總數量一樣,但是卻是會重複非常多次,所以如果能夠紀錄走過的路,那就可以優化了! 這邊使用 map 來儲存,因為除二的結果不重複的數量大概為 $logn$ 但是如果想用陣列儲存會因為 n 太大沒辦法存,所以只能使用速度較慢的 map 來存,但好在數量小,所以不太影響。 ---- ### 實作 ```cpp= const int mod = 1e9 + 7; map<long long,long long> mp; long long fib(long long n) { if (n == 0) return 0; if (n <= 2) return 1; if (mp.find(n) != mp.end()) return mp[n]; if (n % 2 != 0) { long long left_ret = fib((n+1)/2 - 1); long long right_ret = fib((n+1)/2); long long total_ret = (left_ret*left_ret + right_ret*right_ret) % mod; mp[n] = total_ret; return total_ret; } else { long long left_ret = fib(n/2 - 1); long long right_ret = fib(n/2); long long total_ret = (2*left_ret + right_ret)*right_ret % mod; mp[n] = total_ret; return total_ret; } } ``` ---- ### 時間複雜度 $O(logn)$ 因為每次都除二,所以總數量大概為 logn 雖然 map 的查詢,儲存時間為 $O(NlogN)$ 但是因為 $N = logn$,和題目總數 n ~ 1E12,實在是小到可以忽略,可以當成 O(1) (嚴謹一點當然可以寫成 $O(logn \times loglogn)$,放去主定理推也是可以得到一樣的結果) #### 主定理 分成了左右兩半,但另外一邊反而會因為記錄過所以不會重複算到 $T(n) = T(n/2) + O(1)$ 根據情況二, ε = 0,可得時間複雜度為 $O(logn)$ ---- ### 實測 當然也可以用程式碼來粗估複雜度 ```cpp= #include <bits/stdc++.h> using namespace std; const int mod = 1e9 + 7; map<long long,long long> mp; int times = 0; long long fib(long long n) { if (n == 0) return 0; if (n <= 2) return 1; if (mp.find(n) != mp.end()) return mp[n]; times ++; if (n % 2 != 0) { long long left_ret = fib((n+1)/2 - 1); long long right_ret = fib((n+1)/2); long long total_ret = (left_ret*left_ret + right_ret*right_ret) % mod; mp[n] = total_ret; return total_ret; } else { long long left_ret = fib(n/2 - 1); long long right_ret = fib(n/2); long long total_ret = (2*left_ret + right_ret)*right_ret % mod; mp[n] = total_ret; return total_ret; } } void solve(){ long long n; mp.clear(); times = 0; cin >> n; if (n == 0) { cout << 0 << endl; }else{ cout << fib(n) << endl; } cout << "times = " << times << endl; cout << "----" << endl; return; } signed main(){ ios::sync_with_stdio(0), cin.tie(0); long long t = 1; cin >> t; while(t--){ solve(); } } ``` --- ### 優缺點 ---- ### 優點 較為直觀,就是核心三步驟(分割,求解,合併),通用問題全部都可以用。 ---- ### 缺點 分治法的題目較為難寫,同時並不是說分治下去的時間複雜度就會好,還是需要去評估的。 另外,雖然複雜度一樣,但只要有分出去的動作,就是一種 cost ,所以正常情況下,如果有不需要分治的狀況,分治的常數往往都是較差的一方。 ---- ### 常見優化方式 - 在分到量很小的時候,可以考慮暴力計算 - 注意是否會遇到相同子問題,可以儲存起來。 ---- ### 比較 (貪心,DP,分治) | | 貪心 | 動態規劃 | 分治法 | | -------- | -------- | -------- | -------- | | 類型 | 優化問題 | 優化問題 | 通用問題 | | 子問題結構 | 一個子問題 | 很多子問題 (不獨立) | 很多子問題 (獨立) | | 最優子結構 | 必須滿足 | 必須滿足 | 不須要 | | 子問題數 | 解決一個子問題 | 全部都要解決 | 全部都要解決 | | 子問題在最優解中 | 部分 | 部分 | 全部 | | 選擇/求解次數 | 先選擇再解決問題 | 先解決子問題再選擇 | 先選擇再解決問題 | ---- ### 總結 分治對於初學者來說較有難度,但是對於基本功,看待遞迴複雜度很有幫助。 分治法有很多延伸,像是線段樹、CDQ分治法、DP優化,FFT,NTT,Karatsuba,甚至數值方法中也很常用像是勘根法,也都是建立在分治上,可以說是非常實用(且困難)的算法。 ---- ### 練習 - J001 - J002 - I009 (費式數列用分治法求解) 挑戰題 (經典題) - J003

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully