
1. 項目概述當數學建模遇上城市“血管”規劃自來水管道鋪設聽起來像是市政工程或給排水專業的活兒怎么就成了數學建模的經典賽題這恰恰是數學建模的魅力所在——它用抽象的數學工具去解決現實世界中那些看似龐雜、充滿約束的實際問題。我參加過不少數學建模競賽也帶過學生團隊發現“管道鋪設”這類問題幾乎是各類賽事的“常客”因為它完美融合了圖論、優化算法和成本分析是一個檢驗綜合能力的絕佳場景。簡單來說這個問題就是給定一片區域比如一個新開發區、一個工業園區或一個居民小區里面有若干個需要供水的用戶點居民樓、工廠等以及一個或多個水源點水廠、加壓站。你的任務就是設計一套管道網絡用最經濟的方式把水從水源送到每一個用戶點。這里的“經濟”通常指管道建設總成本最低而成本又和管道的長度、材質、直徑等因素掛鉤。這可不是隨便畫畫連接線那么簡單它涉及到如何選擇管道路徑、如何確定管道規格、如何在多個水源和用戶之間分配流量是一個典型的網絡優化問題。無論是參加“高教社杯”全國大學生數學建模競賽還是準備美賽MCM/ICM這類問題都極具代表性。它考察的不僅僅是數學公式的套用更是將實際問題轉化為數學模型的能力、對多種算法如最小生成樹、最短路徑、線性/非線性規劃的靈活運用以及利用編程工具如MATLAB、Python進行求解和結果可視化的綜合技能。接下來我就以一個資深建模者的視角帶你層層拆解這個問題的核心分享從問題分析到論文成稿的全流程實戰經驗。2. 問題拆解與模型構建思路面對一個具體的管道鋪設問題第一步不是急著寫代碼或查公式而是靜下心來把題目描述“翻譯”成數學語言。這個過程決定了整個模型的走向和最終方案的優劣。2.1 核心要素抽象化首先我們需要從工程問題中提取出數學模型的基本元素節點將水源點、用戶需求點抽象為網絡中的“節點”。通常水源點被視為供應點或“根”節點用戶點被視為需求點。有時為了路徑更優我們還可以在道路交叉口或特定位置引入“虛擬節點”或“ Steiner點”斯坦納點但這會大大增加問題復雜度屬于進階玩法。邊連接兩個節點的管道抽象為網絡中的“邊”。每條邊有兩個關鍵屬性長度和成本系數。成本系數可能是一個簡單的單位長度造價也可能是一個與管道直徑、材質、埋深相關的復雜函數。權重通常指邊的成本即鋪設這條管道所需的費用。最基本的情況是成本 單位長度造價 × 管道長度。但實際問題中成本可能還與地形平地、山地、穿越河流、地質條件、是否需拆遷等因素有關這就需要為不同的邊賦予不同的單位成本。約束條件這是模型的血肉。常見的約束包括連通性約束必須保證從水源點到每一個用戶點都存在通路即網絡是連通的。流量約束如果考慮水力管道直徑需滿足用戶節點的流量或水壓需求。這會將問題從單純的圖論問題升級為網絡流優化問題。樹狀網絡約束在很多簡化模型中要求最終的管道網絡是一棵樹即任意兩點間只有唯一路徑無環。這是因為環狀管網雖然可靠性高但初期建設成本通常更高。競賽題中為簡化問題常默認采用樹狀結構。單/多水源是一個水源供應所有點還是多個水源協同供應。目標函數我們最終要優化的東西。99%的情況下是最小化管道網絡的總建設成本。在考慮流量和管徑時目標函數可能是“最小化總投資管道成本泵站成本等”。2.2 模型類型選擇與思路演進根據問題的具體條件我們可以選擇不同復雜度的模型基礎版不考慮流量和管徑的靜態最小成本網絡這是最經典的版本。問題退化為給定節點坐標和邊的造價求一個連接所有節點的網絡使得總成本最小。如果網絡必須是樹那就是經典的最小生成樹問題。常用的算法有Prim算法從一個根節點水源開始逐步生長出一棵樹。非常適合單水源問題能保證生成的樹以水源為根。Kruskal算法對所有邊按權重排序從小到大選擇不構成環的邊加入。更適合無指定根節點或需要快速理解的情況。注意如果問題沒有強制要求樹狀結構那么最優解可能是“最小生成樹”因為增加任何邊都會增加成本在邊權為正的前提下。所以即使題目沒說最小生成樹也常常是一個優秀的候選解。進階版考慮流量需求的管徑優化網絡一旦引入用戶節點的用水量需求問題就變得復雜起來。管道直徑的選擇直接影響成本和輸水能力。大管徑成本高但水頭損失小小管徑成本低但可能無法輸送足夠流量或導致末端水壓不足。這時模型變成一個混合整數非線性規劃問題決策變量包括哪些邊被選中0-1變量、被選中邊的管徑離散選擇變量、管道中的流量連續變量。目標函數是管道成本與管徑、長度有關的最小化約束包括節點流量平衡方程、管道壓降方程等。求解這類問題需要用到專業的優化求解器如LINGO、Gurobi或設計啟發式算法。高階版多階段規劃或帶可靠性要求有些題目會考慮城市規劃是分階段的或者要求管網系統具備一定的可靠性如當某段管道損壞時能有備用路徑供水。這就涉及到多階段優化和網絡可靠性設計可能需要用到更復雜的隨機規劃或魯棒優化模型。對于大多數競賽場景能把“基礎版”做扎實并嘗試向“進階版”做一些合理的探索和簡化就已經能產出非常出色的論文了。我的建議是先完成再完美。先用最小生成樹給出一個可行解作為論文的基準方案然后再嘗試加入流量因素進行優化對比結果展示你的思考深度。3. 核心算法實現與編程實戰理論分析之后就要動手實現。這里我以最經典的、基于坐標和歐氏距離的最小生成樹問題為例展示用Python搭配networkx和matplotlib庫的完整求解和可視化流程。假設我們有1個水源點和9個用戶點。3.1 數據準備與問題描述首先我們定義節點。通常水源點編號為0。import numpy as np import matplotlib.pyplot as plt import networkx as nx # 定義節點坐標 (水源點 用戶點) # 節點0為水源其余1-9為用戶點 coordinates { 0: (0, 0), # 水源 1: (2, 3), 2: (4, 1), 3: (5, 4), 4: (7, 2), 5: (8, 5), 6: (3, 6), 7: (6, 7), 8: (1, 8), 9: (9, 9) } # 定義節點類型和需求為進階模型準備 node_demand {0: 0} # 水源供應需求為負或0 for i in range(1, 10): node_demand[i] np.random.randint(10, 30) # 隨機生成用戶點用水需求單位升/秒3.2 構建完全圖并計算邊權我們的目標是連接所有節點因此先假設任意兩點間都可以直接鋪設管道即構建一個完全圖邊的權重就是兩點間的歐氏距離乘以一個單位成本系數。這里為了簡化設單位成本系數為1即權重等于距離。# 創建完全無向圖 G nx.Graph() G.add_nodes_from(coordinates.keys()) # 計算所有節點對之間的歐氏距離作為邊權成本 for i in coordinates: for j in coordinates: if i j: # 避免重復添加邊 x1, y1 coordinates[i] x2, y2 coordinates[j] distance np.sqrt((x2 - x1)**2 (y2 - y1)**2) # 這里可以乘以一個成本系數例如不同地形成本不同 cost distance # 假設單位距離成本為1 G.add_edge(i, j, weightcost) # 為節點添加坐標屬性用于繪圖 for node, (x, y) in coordinates.items(): G.nodes[node][pos] (x, y)3.3 應用最小生成樹算法求解我們分別使用Prim算法指定水源為根和Kruskal算法求解并對比結果。在單位成本一致的情況下它們求出的總成本應該相同但樹的結構可能因算法而異不過最小生成樹的總權重唯一。# 使用Kruskal算法求最小生成樹 (MST) mst_kruskal nx.minimum_spanning_tree(G, algorithmkruskal) total_cost_kruskal sum(G.edges[u, v][weight] for u, v in mst_kruskal.edges()) # 使用Prim算法求最小生成樹指定根節點為0水源 mst_prim nx.minimum_spanning_tree(G, algorithmprim) total_cost_prim sum(G.edges[u, v][weight] for u, v in mst_prim.edges()) print(fKruskal算法得到的最小生成樹總成本: {total_cost_kruskal:.2f}) print(fPrim算法得到的最小生成樹總成本: {total_cost_prim:.2f})3.4 結果可視化與方案展示將原始節點、可能的連接以淺色表示以及最終選中的最小生成樹管道以粗線高亮繪制出來能讓你的論文結果部分增色不少。# 繪制圖形 plt.figure(figsize(12, 6)) # 子圖1原始完全圖 plt.subplot(1, 2, 1) pos nx.get_node_attributes(G, pos) nx.draw_networkx_nodes(G, pos, node_colorlightblue, node_size300) nx.draw_networkx_nodes(G, pos, nodelist[0], node_colorred, node_size500, label水源) # 高亮水源 nx.draw_networkx_edges(G, pos, alpha0.2, edge_colorgray) # 淺色表示所有可能連接 nx.draw_networkx_labels(G, pos) plt.title(原始節點與所有可能連接完全圖) plt.axis(equal) plt.legend() # 子圖2最小生成樹方案以Prim結果為例 plt.subplot(1, 2, 2) nx.draw_networkx_nodes(G, pos, node_colorlightblue, node_size300) nx.draw_networkx_nodes(G, pos, nodelist[0], node_colorred, node_size500, label水源) nx.draw_networkx_edges(G, pos, edgelistmst_prim.edges(), width3, edge_colordarkorange, label鋪設管道) nx.draw_networkx_labels(G, pos) plt.title(f最小生成樹管道鋪設方案\n總成本: {total_cost_prim:.2f}) plt.axis(equal) plt.legend() plt.tight_layout() plt.show() # 輸出詳細的邊信息 print(\n 最小生成樹管道鋪設詳情 ) print(起點-終點 | 長度成本) print(- * 30) for u, v in mst_prim.edges(): length G.edges[u, v][weight] print(f {u:2d} - {v:2d} | {length:.2f})實操心得在競賽中數據往往不是直接給坐標而是給節點間的距離矩陣或者給的是街區距離曼哈頓距離而非直線距離。這時你需要根據題目描述在構建圖的時候正確計算邊權。例如如果是街區距離邊權cost abs(x2-x1) abs(y2-y1)。這個細節是很多新手容易忽略的失分點。4. 模型進階引入流量與管徑優化基礎模型假設所有管道成本只與長度有關這顯然不符合工程實際。管徑越大造價越高但輸水能力也越強。接下來我們嘗試在模型中加入流量因素做一個簡化版的管徑優化。4.1 問題簡化與假設為了在競賽有限時間內使問題可解我們通常需要做出合理簡化管徑離散化假設市場上只有幾種標準管徑可供選擇如DN100, DN150, DN200而不是連續變量。水力模型簡化忽略復雜的水頭損失計算采用一個簡化的規則例如“某管徑下最大允許流量”。或者我們可以將“滿足流量需求”轉化為對管道“容量”的約束。流向確定在樹狀網絡中從水源到每個用戶的路徑是唯一的。因此每條管道中的流量等于所有通過該管道供水的下游用戶的需求量之和。這是一個自頂向下的確定過程。4.2 建立混合整數規劃模型思路設x_{ij}^vvp75rlhf為0-1變量表示節點i到j的管道是否采用管徑d。f_{ij}為連續變量表示從i流向j的流量假設方向從i到j。c_vvp75rlhf為單位長度、管徑為d的管道成本。L_{ij}為節點i到j的距離。D為所有可選管徑的集合。Q_{max}^vvp75rlhf為管徑d的最大允許流量。目標函數最小化總成本Minimize Σ_{i,j} Σ_{d in D} (c_d * L_ij * x_{ij}^vvp75rlhf)約束條件網絡結構約束選中的邊構成一棵以水源為根的樹。這可以用經典的“單商品流”約束或“Miller-Tucker-Zemlin”約束來刻畫但較為復雜。在編程求解時一種實用的啟發式方法是先確定拓撲結構如用最小生成樹再優化管徑。即兩階段法。流量平衡約束對于每個非水源節點j流入流量 - 流出流量 該節點需求。管徑容量約束如果從i到j的管道被選中且管徑為d則流量f_{ij} Q_max^d。這可以表示為f_{ij} Σ_{d in D} (Q_max^d * x_{ij}^vvp75rlhf)。管徑唯一性約束對于每條可能被選中的邊(i,j)只能選擇一種管徑Σ_{d in D} x_{ij}^vvp75rlhf 1。注意這里是1而不是1因為這條邊可能不被選中。4.3 兩階段啟發式算法實現示例由于完整的MIP模型求解較慢我們可以采用一個高效的兩階段啟發式方法第一階段用最小生成樹確定管道網絡的拓撲結構即哪些邊需要鋪設。第二階段在固定的樹狀結構上從葉子節點向水源根節點回溯計算每條邊需要承載的流量然后根據流量為其選擇能滿足要求的最小標準管徑因為成本隨管徑增大而增加所以選最小可行管徑是最經濟的。# 假設我們已通過第一階段得到最小生成樹 mst_prim (一個networkx的Graph對象) # 定義管徑選項及其屬性 pipe_options { DN100: {max_flow: 15, cost_per_unit_length: 10}, # 最大流量15單位長度成本10 DN150: {max_flow: 30, cost_per_unit_length: 18}, DN200: {max_flow: 50, cost_per_unit_length: 25} } # 將樹轉為以水源節點0為根的有向樹方便計算流量 directed_tree nx.dfs_tree(mst_prim, source0) # 計算每條邊需要承載的流量自底向上后序遍歷 # 首先初始化所有節點的“子樹總需求”葉子節點就是自身需求 subtree_demand node_demand.copy() # 開始時每個節點的子樹需求就是自身需求 # 按從葉子到根的順序處理節點逆拓撲序 # 獲取一個逆拓撲序從葉子到根 reverse_order list(reversed(list(nx.topological_sort(directed_tree)))) # 有向樹拓撲排序 for node in reverse_order: if node 0: continue # 根節點水源不需要向上傳遞 # 找到該節點的父節點在有向樹中入度為1 predecessors list(directed_tree.predecessors(node)) if predecessors: parent predecessors[0] # 將當前節點的子樹總需求加到父節點的子樹總需求上 subtree_demand[parent] subtree_demand[node] # 現在對于有向樹中的每條邊 (parent - child)其需要承載的流量就是 child 的 subtree_demand edge_flow {} for u, v in directed_tree.edges(): # u是父v是子 edge_flow[(u, v)] subtree_demand[v] # 根據流量為每條邊選擇管徑 edge_diameter {} total_cost_advanced 0 print(\n 考慮流量后的管徑優化方案 ) print(邊父-子 | 所需流量 | 選定管徑 | 長度 | 該段成本) print(- * 60) for (u, v), flow in edge_flow.items(): length G.edges[u, v][weight] # 獲取邊的長度 # 選擇能滿足流量的最小成本管徑 chosen_diam None min_cost_for_edge float(inf) for diam, props in pipe_options.items(): if flow props[max_flow]: cost props[cost_per_unit_length] * length if cost min_cost_for_edge: min_cost_for_edge cost chosen_diam diam # 理論上管徑選項應覆蓋所有流量范圍這里假設總能找到 edge_diameter[(u, v)] chosen_diam total_cost_advanced min_cost_for_edge print(f {u:2d} - {v:2d} | {flow:7.1f} | {chosen_diam:^8} | {length:4.2f} | {min_cost_for_edge:7.2f}) print(f\n優化后管網總成本: {total_cost_advanced:.2f}) print(f相較于僅考慮長度的基礎方案成本 {total_cost_prim:.2f}成本變化: {total_cost_advanced - total_cost_prim:.2f})注意這個兩階段方法是啟發式的不一定得到全局最優解因為拓撲結構是第一階段單獨決定的可能不是考慮管徑成本后的最優拓撲。但在時間有限的競賽中這是一個非常有效且合理的策略。你可以在論文中明確指出這是“分解-優化”的啟發式思路并討論其優缺點。5. 論文寫作要點與常見陷阱數學建模競賽三分靠模型七分靠表達。一個清晰、嚴謹、美觀的論文是獲勝的關鍵。5.1 論文核心結構摘要重中之重用300字左右概括問題、你的方法、模型、算法、主要結果和結論。要獨立成文讓評委不看正文也能知道你們做了什么。模板“針對XX問題本文建立了……模型。首先……其次……然后運用……算法求解最后……。結果表明……。本文的特色在于……。”問題重述與分析不要照抄題目要用自己的話梳理問題的背景、條件和目標。進行問題分析指出問題的類型優化、預測、評估等、關鍵難點和解決思路。模型假設與符號說明列出所有為了簡化問題而做出的合理假設。符號說明用三線表清晰明了。模型建立與求解這是論文主體。分小節闡述你的模型5.1 基礎模型如最小生成樹模型5.2 模型改進如引入流量與管徑的優化模型5.3 算法設計詳細說明你用的算法步驟最好配上流程圖5.4 求解過程說明使用的軟件、工具包、求解器參數等模型結果與分析用表格和圖表展示結果總成本、管道明細表、網絡圖。進行靈敏度分析改變某個參數如單位成本、某個用戶需求觀察結果如何變化。這能體現模型的穩健性和你的思考深度。例如“將水源點坐標微調至(0.5, 0.5)總成本變化小于2%表明模型對水源位置不敏感。”方案對比如果你的模型有多個版本如基礎版vs進階版一定要對比結果分析差異原因。模型評價與推廣客觀評價自己模型的優點計算快、易于理解、結果合理和缺點忽略了地形起伏、假設需求恒定等。提出模型的改進方向和在類似問題如電網鋪設、通信光纜布局中的應用前景。參考文獻與附錄規范引用參考文獻。將核心代碼、大型數據表格放在附錄。5.2 常見陷阱與避坑指南陷阱一模型與算法混淆。在論文中“模型”是指你用數學公式、變量、約束描述的問題結構“算法”是求解這個模型的具體計算步驟如Prim算法、遺傳算法。一定要分開寫。陷阱二只有結果沒有分析。擺出總成本就完了不行要分析這個方案為什么好管道走向有什么特點成本主要花在哪里。靈敏度分析是拉開差距的關鍵。陷阱三圖表質量低下。用MATLAB或Python畫圖時務必保證清晰度。坐標軸標簽、圖例、標題要齊全。網絡圖節點不要重疊可以用nx.spring_layout或nx.kamada_kawai_layout進行布局優化。陷阱四忽略單位。長度是米還是公里成本是元還是萬元流量是升/秒還是立方米/天全文必須統一并在符號說明中明確標出。陷阱五代碼堆砌。附錄里的代碼要有重點只放核心函數或算法主流程并加上必要的注釋。不要粘貼全部幾十行代碼。陷阱六假設不合理。假設是為了簡化但不能改變問題的本質。例如不能假設“所有用戶需求為零”來簡化問題。你的假設需要讓人感覺是“在可控范圍內對現實情況的合理近似”。個人心得管道鋪設問題是一個非常好的練手項目它像一把鑰匙能幫你打開運籌學、圖論和優化算法的大門。在實戰中最關鍵的一步永遠是“問題分析”。花半小時仔細讀題、畫示意圖、列舉已知和未知遠比一上來就套公式有效。當你拿到題目看到那些散落的“點”能在腦海里自動將它們連成一張“圖”并開始思考“權重”和“約束”時你就已經成功了一半。剩下的就是用嚴謹的數學和清晰的表達將你的思考呈現出來。