<style> .reveal .slides { text-align: left; font-size:32px; } </style> ## Special Graph & 0-1 BFS & 差分約束 #### 6/26 Advanced Competitive Programming ---- - 0-1 BFS - Special graph - Functional graph - Jellyfish graph - Cactus graph - 差分約束系統 --- ## 0-1 BFS 在權重只有 0 和 1 的圖上做 BFS ---- 在邊權只有 1 的情況下,可以用最一般的 BFS 計算最短路徑 $\rightarrow O(\vert E \vert )$ 但是在邊權只有 0 或 1 的情況下,可以用 0-1 BFS 來做,複雜度一樣為 $O(\vert E \vert )$ 可以降低 dijkstra 的複雜度 $O(E \log V)$ ---- ## 邊權只為 1 的 BFS 在一般的 BFS 的 queue 裡面 前面會有一段 dis 為 D 後面會有一段為D+1 ``` queue : [a...b ,c.....d] dis : [D...D ,D+1...D+1] ``` 每次會拿 queue 最前頭的點和 dis 繼續往新的點 BFS , dis 更新成D+1,放到 queue 的最後面 ---- ## 0-1 BFS 當圖的邊權分成 0 或 1 ``` queue : [a...b ,c.....d] dis : [D...D ,D+1...D+1] ``` 要拿最前頭的點 dis = D 時,需要分成兩種情況 - 邊權為0 $\rightarrow$ 新的 dis 為 D - 邊權為1 $\rightarrow$ 新的 dis 為 D+1 由於使用的 STL 為 queue , 遇到新的 dis 為 D 時,把新的 {點, dis} 塞到 queue 的最後面會出問題 ---- 因此可以考慮改用 deque ,把邊權為 0 的 {點, dis} 放到最前面 ``` D D+1 v v deque : [a...b ,c.....d ] dis : [D...D ,D+1...D+1] ``` ---- ```cpp= deque<int> que; que.push(s); while(!que.empty()){ int u = que.front(); que.pop(); if(vis[u]) continue; vis[u] = 1; for(int [v, w] : edge[u]){ if(dis[v] > dis[u] + w){ dis[v] = dis[u] + w; if(w == 1) que.push_back(v); else que.push_front(v); } } } ``` 每個邊只會跑過一次,時間複雜度:$O(\vert E \vert)$ ---- ### 例題:CodeChef - Chef and Reversing 給一張有向無權圖,求最少要翻轉幾條邊能從節點 1 走到節點 n ![image](https://hackmd.io/_uploads/BkhI27xqp.png) 上圖答案為 2 ---- 對於一條有向邊 $(u, v)$ 分別建兩條邊 $(u, v, 0)$,走原本給的路 $(v, u, 1)$,翻轉該邊 然後跑最短路,最短路的成本即為翻轉的次數 ---- ### Atcoder - abc246E. Bishop 2 給一個 $n\times n$ 的西洋棋盤棋盤 有一些格子是障礙物,給你一隻主教在位置 $(s_x, s_y)$ 主教每次可以往對角線方向移動任意步 (不能穿越障礙物) 求移動到位置 $(e_x, e_y)$ 要幾次 $1\le n\le 1500$ ---- $s = (1, 3)$ $e = (3, 5)$ ``` ..I.X ....X ....X ....X ...X. .I.X. ...X. ...X. ..... -> ..... -> ..... -> ....I .X... .X... .X.I. .X... X.... X.... X.... X.... ``` $(1, 3)\to (2, 2)\to (4,4)\to (3, 5)$ 共3次 ---- 如果每次都暴力把四個方向可以走的位置都更新一遍, 那複雜度很明顯是爛的 $O(n^3)$ 想一下如何套 0-1 BFS 到這題上? ---- 分成兩種 case 1. 往其中一個方向走一單位,花費 1 2. 延續剛剛走的繼續走,花費 0 ``` ...... .1.1.. ..S... .1.1.. ...... ...... ``` ``` 0...0. .1.1.. ..S... .1.1.. 0...0. .....0 ``` ---- 即可轉變為 0-1 BFS ! 實作上的細節要分成四個方向記錄是否可以走 ---- ## Dial's algorithm 而如果所有的邊權重都 $\le k$, 也可以開 $k+1$ 個 queue 來分別維護從當前距離 $v$ 開始一直到距離 $v+k$ 的更新點 每當距離 $v$ 的 queue 空了就位移到下一個 queue,然後環狀的維護這些 queue 複雜度最差為 $O(nk)$ --- ## Functional Graph 一張圖,每個點各有剛好一條出去的邊(出度為1) ![image](https://hackmd.io/_uploads/H15FGEN80.png) ---- ## Functional Graph 的特性 一張 Functional Graph 的連通子圖會由剛好一個環+一堆連向環的邊組成 ![image](https://hackmd.io/_uploads/ryLLHN48R.png =400x) 圖中任意一個點一定有下一個點可以走,也被稱為 succesor graph ---- ## Functional Graph 經典例題 給一個Functional Graph,$Q$ 筆詢問 問點 $x$ 一直走 $k$ 步會到哪個點 ![image](https://hackmd.io/_uploads/HktctEVLA.png) succ(2,2)=4 succ(3,4)=4 ---- 當然可以直接 DFS 往下走 k 步,這樣會花 $O(k)$ 的時間,有點太爛了 所以我們考慮 $O(\log k)$ 的作法 ---- 考慮倍增法,一樣先建出每一個點往下走 $2^0,2^1,2^2,....,2^k$步會到哪裡 當要從 $x$ 點走 $k$ 時 會將 $k$ 二進制分解 走 $k=11$步 $\rightarrow$ 走 $8+2+1$ 步 ---- ![image](https://hackmd.io/_uploads/SJQI9yl5p.png) 從 $2$ 開始走 $11$ 步 succ(2,11) =succ(succ(2,8),3) =succ(succ(succ(2,8),2),1) =succ(succ(4,2),1) =succ(3,1) =4 ---- 因此只要建出倍增表,是先處理每個點跳 $2^0,2^1,....2^k$ 會到哪個點 就可以在 $O(n \log n + q \log k)$ 解決這題 ---- ## Functional Graph 找環 可以從任意點DFS並紀錄每個點有沒有走過,如果走到了走過的點,代表有環 記錄走過+DFS,時間&空間複雜度 : $O(n)$ ---- 先看個[影片](https://www.youtube.com/watch?v=pKO9UjSeLew) ---- 剛剛的影片中要求時間複雜度 $O(n)$ , 空間複雜度 $O(1)$ 顯然不能DFS 接下來介紹一下影片最後的作法是怎麼做的 ---- ## Floyd's Algorithm 在 Functional graph 判環的方法 可以用時間複雜度 $O(n)$ + 空間複雜度 $O(1)$ 解決 遇到題目限制 $n$ 很大 或是有限制空間複雜度就要用這個演算法 ---- 又稱龜兔賽跑演算法 放兩個指針,一快一慢放在圖上某個點慢慢走 這兩個指針一定會在環上某個點相遇 ---- 讓烏龜指針(慢指針)一次走一步 兔子指針(快指針)一次走兩步 ![image](https://hackmd.io/_uploads/SJ9W57rI0.png) ---- ![image](https://hackmd.io/_uploads/Sy3S97SIC.png) ---- ![image](https://hackmd.io/_uploads/BJgwqXB8C.png) ---- ![image](https://hackmd.io/_uploads/BymJo7SU0.png) ---- ![image](https://hackmd.io/_uploads/rJdxsQH8A.png) ---- ![image](https://hackmd.io/_uploads/BJ1GjXB80.png) 在環上會合了 但是這還不夠,有些題目可能還會問你「環的長度」或是「哪一個環上的點離 $x$ 最近」 ---- 這時我們想要知道哪一個環上的點離 $x$ 最近 ![image](https://hackmd.io/_uploads/B1OJaXr8A.png) ---- 作法會是把兔子指針移回點 $x$,烏龜指針留在原地 ,然後叫兔子指針走慢一點(一次一步) 根據某[數學證明](https://youtu.be/9YTjXqqJEFE?si=MldyHt3nnixfyKzZ)可以知道它們繼續走必定會在紅點會合 ![image](https://hackmd.io/_uploads/r1WExVBUR.png) ---- 再來找環的長度,把烏龜指針放著,兔子指針慢慢繞一圈 記錄繞回烏龜走了幾步 ![image](https://hackmd.io/_uploads/SkmYbErLR.png) 以上做法都是 時間複雜度$O(n)$ + 空間複雜度$O(1)$ --- ## Jellyfish Graph 長得像水母的圖 由一個環和一些樹組成的圖 正好為無向的 Functional Graph ![image](https://hackmd.io/_uploads/B1qzFVg96.png =x300 )![image](https://hackmd.io/_uploads/rkEgr4S8A.png =x300) ###### 圖片取自《夜晚的水母不會游泳》好看 大推 ---- 常見的題型為在圖上做樹上DP 像是水母圖上最小點覆蓋,最大獨立集 ---- ### 淺談最小點覆蓋 最小點覆蓋:最小的點集使得圖上每條邊都至少與點集中一個點相鄰 ![image](https://hackmd.io/_uploads/rJ_rKHHL0.png =400x) 圖中最小點覆蓋為4個點 ---- ### [ICPC 2020 台北站 F. Cable Protection](https://drive.google.com/file/d/1v6vk-VEtyNNDbTPOlc1JE_m9VllTHwi0/view) 給一張水母圖,求最小點覆蓋。 ![image](https://hackmd.io/_uploads/S1gBSnSSLC.png) ---- 把環上延伸出去的樹去做樹上 DP 分兩種狀態 : 根節點被選 / 根結點沒被選 可以用BFS 每次取度數為 1 的葉節點從下往上更新 DP 值 ![image](https://hackmd.io/_uploads/Bk812BSUC.png =450x) ---- 接著就變成環上問題 留給大家想想:D --- ## Cactus graph 仙人掌圖,圖如其名,一球一球的 ![image](https://hackmd.io/_uploads/r1md5Egcp.png =x300) ![image](https://hackmd.io/_uploads/HJ9QcEg56.png =x300) ---- ## 仙人掌圖的定義 仙人掌圖有很多可能的定義,根據題目給定 - 每條邊在恰好一個簡單環上 - 每條邊在最多一個簡單環上 ![image](https://hackmd.io/_uploads/r1md5Egcp.png =x300) ![image](https://hackmd.io/_uploads/Skrx3Ve9a.png =x300) ---- ## 經典問題 --- 判斷一張圖是否為仙人掌圖 判斷每條邊是否在恰好一個環上 ---- ### 作法 DFS,用 stack 維護當前遍歷的節點, 走到重複的節點就回退,把這個環上的點都記錄+1。 ( 環的起點不加,因為一個點可以在多個環上 ) 如果有節點被記錄超過 1 則此圖不是仙人掌圖。 ![image](https://hackmd.io/_uploads/BJH4xre9T.png =300x) <font color="#303030"><small>或者砸 SCC/BCC 模版在判斷每個環是否為簡單環</small></font> ---- ## ~~出題法則~~ ~~如果想把序列上的題目加難~~ $\to$ ~~就把他出在樹上~~ ~~如果想把樹上的題目加難~~ $\to$ ~~就把他改成仙人掌圖~~ --- ## 差分約束系統 ### Constraint Difference System 是求解關於一組不等式之方法。 $n$ 個變數和 $m$ 個約束條件組成 其中每個約束條件形如 ${ x_{j}-x_{i}\leq b_{k}(i,j\in [1,n],k\in [1,m])}$ 則稱其為差分約束系統。 ---- ## 例子 ${x_{1}-x_{2}\leq 8}$ ${x_{1}-x_{3}\leq 2}$ $\to x_{1}=3, x_{2}=-5, x_{3}=1$ ${x_{1}-x_{2}\leq 1}$ ${x_{1}-x_{3}\leq -1}$ ${x_{3}-x_{2}\leq 0}$ $\to$ 無解 <div style="position: absolute; right: 10%; top:0%;"> ![](https://i.imgur.com/uA2FxQR.png =250x) </div> <div style="position: absolute; right: 10%; top:80%;"> ![](https://i.imgur.com/EaMjJwm.png =250x) </div> ---- ## 作法 轉成圖論問題 對於所有 $x_{j}-x_{i}\leq k$ 連接一條邊 $(i, j)$,權重為 $k$ 最後再設置一個起點 $s$,連向所有邊邊權為 $0$ 從起點 $s$,跑 Bellman Ford/SPFA , 若出現負環則代表這組不等式無解 ---- ## 建圖 ${x_{2}-x_{1}\leq 1}$ ${x_{1}-x_{3}\leq -1}$ ${x_{3}-x_{2}\leq 0}$ ![](https://i.imgur.com/LySYtE5.png) ---- ## 結果 跑完 Bellman Ford/SPFA 如果發現有負環,則此組不等式無解 否則所有點到起點的距離為其中一組解 ---- ## 變化 | 限制 | 建邊 | | -------- | -------- | | $x_{j}-x_{i}\leq k$ | addEdge($i, j, k$) | | $x_{j}-x_{i}\geq k$ | addEdge($j, i, -k$) | | $x_{j} = x_{i}$ | addEdge($i, j, 0$), addEdge($j, i, 0$) | ---- ## 區間問題轉換 給 $n$ 個區間 $[l_i, r_i]$ 以及 $c_i$ 要構造出一個序列 $\forall i\in [1, n]$ 滿足區間 $[l_i, r_i]$ 內的總和至少為 $c_i$, 構造出總和最小的序列。 ---- ## 轉換為前綴和處理 $[l_i, r_i]$ 至少為 $c_i$ 等價於前綴和 $pre_r$ - $pre_{l-1}$ 至少為 $c_i$ ---- 等價於前綴和 $pre_r - pre_{l-1}$ 至少為 $c_i$ $\to pre_r - pre_{l-1}\ge c_i$ 把序列上每個位置當成節點,即可轉換為差分約束問題 ! --- 題單連結 : [link](https://vjudge.net/contest/633968) 期限 : 7/24 20:00 要比賽ㄌ 大家暑假好好練><!!!! ![image](https://hackmd.io/_uploads/ByLkr8HLR.png)
{"title":"Functional graph","description":"This note is yours, feel free to play around. :video_game:Type on the left :arrow_left: and see the rendered result on the right. :arrow_right:","contributors":"[{\"id\":\"08326fa4-ca9b-4ca9-873e-239ebe76662c\",\"add\":8156,\"del\":375}]"}
    1029 views
   owned this note