Luogu 🟡 P4017 最大食物链计数
題目的難度顏色使用 Luogu 上的分級,由簡單到困難分別為 🔴🟠🟡🟢🔵🟣⚫。
🔗 P4017 最大食物链计数
Problem Statement
題目簡述
給定一個食物網(有向無環圖),求出其中「最大食物鏈」的數量。
一條「最大食物鏈」定義為:從一個沒有被其他生物捕食的生物(入度為 的節點)開始,一直到一個不捕食其他生物的生物(出度為 的節點)結束的路徑。
答案對 取模。
Constraints
約束條件
思路:拓樸排序與記憶化搜索
這道題目本質上是在一個有向無環圖(DAG)中,尋找從「起點」(入度為 0 的節點)到「終點」(出度為 0 的節點)的路徑總數。
由於圖中沒有環,我們可以使用動態規劃的思想來解決。對於圖中的任意一個節點,到達該節點的路徑數,等於所有能直接走到該節點的前驅節點的路徑數之和。
根據計算順序的不同,我們可以採用兩種常見的實作方式:正向的拓樸排序,或是反向的記憶化搜索。
方法一:拓樸排序 DP
按照拓樸序遍歷節點,確保在計算某個節點時,所有能到達它的前驅節點都已經計算完畢。
我們可以定義一個狀態陣列,用來記錄「從任意起點出發,到達目前節點的路徑總數」。
初始時,將所有起點(入度為 的節點)的狀態值設為 ,其餘節點設為 。
接著,我們使用一個佇列來進行拓樸排序:
- 將所有入度為 0 的節點加入佇列。
- 每次從佇列中取出一個節點,並遍歷它能直接到達的相鄰節點。
- 將目前節點的狀態值累加到相鄰節點的狀態值上(記得取模)。
- 將相鄰節點的入度 。如果相鄰節點的入度變為 ,代表它的所有前驅節點都已處理完畢,將其加入佇列。
當拓樸排序結束後,我們只需要將所有終點(出度為 的節點)的狀態值加總,即為最終答案。
複雜度分析
- 時間複雜度:,其中 為節點數, 為邊數。拓樸排序需要遍歷所有的節點和邊。
- 空間複雜度:,用於儲存圖的鄰接表、入度/出度陣列、狀態陣列以及拓樸排序的佇列。
方法二:記憶化搜索
從起點出發,遞迴地向下尋找終點。為了避免重複計算相同的子問題,將已經計算過的節點結果儲存起來。
我們可以定義一個遞迴函數,其回傳值代表「從目前節點出發,到達任意終點的路徑總數」。
遞迴的終止條件是:如果目前節點是終點(出度為 0),則只有 1 條路徑(即停留在原地),回傳 1。
對於非終點的節點,我們遍歷它能直接到達的所有相鄰節點,將遞迴呼叫相鄰節點的回傳值累加起來,就是目前節點的答案。
為了避免重複計算,我們可以使用記憶化技術(例如 Python 的 @cache 裝飾器),將每個節點的計算結果快取起來。
最後,我們遍歷所有的起點(入度為 0 的節點),將它們的遞迴結果加總,即為最終答案。
複雜度分析
- 時間複雜度:。由於記憶化的存在,每個節點和每條邊最多只會被訪問一次。
- 空間複雜度:。用於儲存圖的鄰接表、入度/出度陣列,以及遞迴呼叫的系統堆疊空間(最壞情況下深度為 )。
Code
方法一:拓樸排序 DP
1 | from collections import deque |
方法二:記憶化搜索
1 | from functools import cache |





![Luogu 🟢 P5937 [CEOI 1999] Parity Game](https://i.gdst.dev/cover/P5937.webp)
![Luogu 🔵 P1966 [NOIP 2013 提高组] 火柴排队](https://i.gdst.dev/cover/P1966.webp)
![Luogu 🔵 P4375 [USACO18OPEN] Out of Sorts G](https://i.gdst.dev/cover/P4375.webp)