學建模B題穿越沙漠:狀態(tài)轉(zhuǎn)移與Matlab動態(tài)規(guī)劃拿分攻略)
簡介2020年數(shù)學建模B題的Matlab源代碼包面向數(shù)學建模競賽參賽者與需要處理多階段決策優(yōu)化問題的研究者。代碼基于4天建模培訓實踐完成完整覆蓋第一關(guān)至第四關(guān)的求解思路前兩關(guān)利用距離矩陣與單目標優(yōu)化模型處理雙地圖、雙村莊的采礦和補給決策后兩關(guān)針對天氣隨機性分別采用天氣概率分布結(jié)合動態(tài)規(guī)劃、數(shù)學期望和隨機分布方法求最優(yōu)路線并對比優(yōu)劣。壓縮包內(nèi)共114個文件以93個m腳本為主含各關(guān)卡主程序與核心算法、15個mat數(shù)據(jù)文件地圖及天氣數(shù)據(jù)、5個asv自動保存?zhèn)浞莺?個xlsx結(jié)果表整體僅66KB輕量易讀。其中對第三關(guān)挖礦效益下降、無沙暴環(huán)境下的遍歷比較等特例也做了實現(xiàn)與說明。目前已有1239人學習下載適合想深入理解動態(tài)規(guī)劃、期望值建模及優(yōu)化問題代碼實現(xiàn)的讀者。1. 2020年數(shù)學建模B題先吃透狀態(tài)轉(zhuǎn)移再談Matlab源代碼拿分2020年數(shù)學建模B題那道“穿越沙漠”很多備賽者第一眼都當成路徑規(guī)劃題真正拿Matlab一跑才發(fā)現(xiàn)卡人的不是“走哪條路”而是“每天要不要挖礦、買多少水、扛不扛得住沙暴”。這恰恰是B題類的最典型考點——把現(xiàn)實約束變成狀態(tài)轉(zhuǎn)移方程。高頻考點集中在模型建立、求解算法、結(jié)果驗證三個模塊配合一套能跑的Matlab源代碼比背一堆理論公式更容易拿分。這篇文章直接按“考什么—高頻考點—真題怎么答—易錯點—現(xiàn)場拿分技巧”的順序拆開適合拿到題只會套模板、或代碼一跑就報錯的備賽者。2. 考點分布與模型建立目標函數(shù)、狀態(tài)變量與得分權(quán)重2.1 這道題到底在考什么模型分通常比代碼分更值錢數(shù)學建模競賽的閱卷邏輯和期末考試不一樣。期末考的是“算得對不對”建??嫉氖恰跋氲们宀磺宄?。像2020年B題這種帶明確場景的題目評閱重點普遍落在三塊模型建立是否完整、求解方法是否匹配、結(jié)果分析是否合理。代碼只是工具Matlab源代碼的作用是讓模型“跑得動”而不是替模型拿分。備賽者最容易犯的錯是急著寫代碼。某選手A第一次做B題類模擬上來就打開Matlab開始寫for循環(huán)寫了兩個小時發(fā)現(xiàn)天氣、物資、位置三個變量互相糾纏狀態(tài)根本存不下。后來某導師給他復盤時說了一句話這類題的第一步永遠是寫狀態(tài)變量不是寫代碼。你連“每天結(jié)束時手里有幾箱水、幾箱食物、人在哪”都沒定義清楚代碼寫得再漂亮也是空殼。所以這道題真正的考點分布可以這樣理解狀態(tài)定義與約束建模約占四成求解算法設(shè)計約占三成結(jié)果分析與論文呈現(xiàn)約占三成。代碼單獨拿出來看未必有分但代碼跑不出結(jié)果模型分也會跟著被扣——因為閱卷人無法通過一張空表判斷你的模型是否有效。2.2 狀態(tài)定義與決策變量動態(tài)規(guī)劃表格怎么搭B題的核心難點是“狀態(tài)爆炸”。玩家每天的決定會改變位置、資金、水和食物的存量而這些因素互相耦合。要建模第一步是把所有可能影響決策的量列出來再判斷哪些變量能壓縮、哪些變量必須保留。以穿越沙漠為例典型狀態(tài)變量包括變量含義取值范圍示例說明t當前天數(shù)1~30決定剩余天數(shù)與天氣p當前位置1~N個節(jié)點起點、村莊、礦山、終點w水存量0~Wmax受負重上限約束f食物存量0~Fmax受負重上限約束m當前資金0~初始資金購買物資和挖礦收入決定q當日天氣晴朗/高溫/沙暴決定行走消耗與能否行動目標函數(shù)很直白到達終點時剩余的資金最大化。約束條件才是真正篩人的地方任何時候水和食物不能為負、負重不能超上限、沙暴天不能移動、物資消耗隨天氣變化。把這些約束寫全模型分就拿到一大半。2.3 真題演練第一問的初始購買策略怎么答第一問最常見的考法是起點有物資商店初始資金有限水和食物都有價格和重量問第一天買多少物資最劃算。這類問題的標準答法分三步先算單位資金的“生存天數(shù)產(chǎn)出”再枚舉幾組購買方案最后用終局資金說話。先說單位資金的生存天數(shù)產(chǎn)出。一箱水有一箱水的重量和持有天數(shù)一箱食物也同理。要把水和食物統(tǒng)一折算成“可以支撐行走多少天”而不是直接比價格。因為天氣不同水和食物的消耗速度不同價格便宜的東西未必劃算。這一步不需要太高深的數(shù)學把題面給的消耗表列出來逐項做除法就能得到每單位資金能換到的“有效生存天數(shù)”。接著做枚舉。初始資金有限購買策略其實只有幾類全買水、全買食物、按比例搭配。每類策略都會產(chǎn)生一組確定的初始狀態(tài)接下來用模擬或者動態(tài)規(guī)劃跑到底比較終點資金。備賽者很容易在這一步犯懶只算“夠不夠走到終點”不算“到了終點還剩多少錢”。第一問的落腳點一定是“剩余資金最大化”不是“能活著到終點”。活著只是門檻富余才是得分點?;卮疬@類問題時建議在論文里同時給出購買數(shù)量表、模擬結(jié)果表和一句結(jié)論比如“在初始資金X元、晴朗天氣占Y%的條件下購買水a(chǎn)箱、食物b箱可使終點資金最大”。閱卷人看到的是完整決策鏈而不是一堆推導公式。3. 高頻考點與Matlab代碼模板動態(tài)規(guī)劃、貪心與隨機天氣處理3.1 反推動態(tài)規(guī)劃一種能避開狀態(tài)爆炸的Matlab實現(xiàn)動態(tài)規(guī)劃是B題類最常用的求解框架但正推要記錄“從起點到當前狀態(tài)的最大剩余資金”中間狀態(tài)非常多。反推則從終點倒著算站在某個狀態(tài)判斷“如果我現(xiàn)在手里有這些資源最多還能剩多少錢到終點”。反推的好處是能自然處理“必須活著到達終點”的約束——從終點往回推時資源不足的狀態(tài)直接標記為不可行。Matlab里實現(xiàn)反推動態(tài)規(guī)劃關(guān)鍵是把狀態(tài)存進結(jié)構(gòu)體或cell數(shù)組而不是用多維數(shù)組硬存。多維數(shù)組很容易因為狀態(tài)維度超過四維而內(nèi)存爆掉這是新手最常見的翻車點。下面給出一個簡化模板邏輯是逐天倒推并記錄最優(yōu)決策%% 反推動態(tài)規(guī)劃模板穿越沙漠類問題簡化版 days 30; % 總天數(shù) state cell(days1, 1); % 每個元素存放當天所有可達狀態(tài) % 狀態(tài)編碼: [位置, 水, 食物, 剩余資金] % 初始化第 T 天終點只有 到達終點 這一種狀態(tài)合法 state{days1} {[終點編號, 0, 0, 資金余額]}; for t days:-1:1 % 從終點前一天往前倒推 newStates {}; % 遍歷第 t1 天的所有狀態(tài)反推第 t 天 for k 1:length(state{t1}) cur state{t1}{k}; % 根據(jù)當天天氣計算從某位置走到下一個位置所需的消耗 % 若前一天在原地停留/挖礦則位置不變、資金按規(guī)則變化 % 判斷 資源是否足夠、負重是否超限 location cur(1); water cur(2); food cur(3); money cur(4); % 示例晴朗天行走一格的消耗 needWater 1; % 按題面消耗表填寫 needFood 1; % 按題面消耗表填寫 if water needWater food needFood % 前一天可行構(gòu)造前一天狀態(tài)并去重 prev [location, waterneedWater, foodneedFood, money]; newStates{end1} prev; %#okSAGROW end end state{t} newStates; end這段模板的核心是“從終點往前補資源”也就是把第t1天的水、食物消耗加回去得到第t天可能的狀態(tài)。注意這里省去了購買、挖礦等操作實際比賽時要根據(jù)決策變量分支展開。對于分支爆炸問題兩個常用技巧一是按“支配關(guān)系”剪枝如果兩個狀態(tài)位置相同、天數(shù)相同一個的水和食物都更多且資金不更少那另一個狀態(tài)可以直接刪除二是把水、食物離散化到整數(shù)箱減少狀態(tài)總量。在答辯或論文里不需要把所有代碼貼出來但要寫明三個參數(shù)狀態(tài)編碼格式、剪枝規(guī)則、邊界條件終點狀態(tài)怎么定義、負資源怎么判死。這樣代碼即使不是全篇最優(yōu)也能證明你“真的想過怎么算”。3.2 貪心與局部搜索結(jié)果不好時的后悔藥動態(tài)規(guī)劃寫出來但跑不動、或者內(nèi)存炸了這是比賽現(xiàn)場的高頻事件。此時不能用動態(tài)規(guī)劃的備賽者通常用貪心救場。貪心的思路很簡單每一天比較“繼續(xù)走”“原地?!薄巴诘V”三件事的凈收益選最大的那個。凈收益要把“當天消耗的資源按價格折算成錢”扣掉不然會高估收益。%% 貪心決策模板比較行動收益 % 輸入?yún)?shù) unitWaterCost 8; % 每箱水價格示例 unitFoodCost 6; % 每箱食物價格示例 % 當前狀態(tài) pos 3; % 當前位置編號 water 20; % 當前水存量 food 18; % 當前食物存量 money 500; % 當前資金 % 當天天氣1晴朗 2高溫 3沙暴 weather 1; % 計算不同行動的綜合開銷以“消耗折錢 收入”為判據(jù) % 示例挖礦的收入與消耗 mineIncome 300; % 挖礦一天收入 mineCost 8*2 6*2; % 挖礦一天消耗折錢具體按題面表 netMine mineIncome - mineCost; % 行走的收益是“向前推進一天”本身不直接產(chǎn)生資金 % 但如果目的地是村莊/終點可能節(jié)省后續(xù)購買開銷 % 綜合判斷沙暴天不能走高溫天行走消耗大 if weather 3 action 停留; % 沙暴天強行走會出局 elseif netMine 0 action 挖礦; else action 行走; end fprintf(第%d天的貪心決策%s\n, t, action);這段代碼看起來簡單實際比賽里很多人會忽略“挖礦凈收益”的長期視角。挖礦一天確實賺錢但挖礦耽誤了行程后面可能不得不在高溫天多走一兩天把賺的錢又賠回去。所以貪心出來的方案通常只是“可行解”不是“最優(yōu)解”。不少隊伍用貪心跑完第一問再用局部搜索優(yōu)化第二問把“挖礦天數(shù)”和“購買批量”當作兩個參數(shù)做小范圍的枚舉調(diào)整結(jié)果能比純貪心明顯更高。在論文里寫貪心一定要寫清楚“為什么貪心在這道題里不會太離譜”。通常給出的理由是行走的消耗隨時間線性累積而挖礦收益固定所以只要比較單位時間的收益與消耗即可。這比直接貼一段代碼更有說服力。3.3 真題演練隨機天氣下沙暴處理怎么答第二問經(jīng)常把天氣改成隨機沙暴天出現(xiàn)的概率已知。這時候“最優(yōu)解”的概念變了你要算的是期望資金最大還得防備小概率的極端情況。很多備賽者直接用第一問的確定性模型套第二問結(jié)果跑出來和答案差一大截原因就是沒有把隨機性納入目標。這類題的標準答法是雙軌先用蒙特卡洛模擬估計期望再用一個保守策略做安全墊。蒙特卡洛的做法不復雜——用隨機數(shù)生成1000組天氣序列每組序列都用貪心或動態(tài)規(guī)劃跑出終局資金最后取平均值和方差。Matlab里的rand配合cumsum就能模擬沙暴天數(shù)不需要額外的工具箱。答題時要讓閱卷人看到兩條結(jié)論一是在不同沙暴概率下期望資金變化曲線二是“多帶水”作為安全墊對結(jié)果的影響。比如沙暴概率從10%升到30%最優(yōu)購水量會從a箱升到b箱這個趨勢比具體數(shù)值更值錢。有隊伍把這個做成一張曲線圖直接放進論文的“靈敏度分析”部分得了不少分。最后提醒一句隨機天氣下的代碼每次跑的結(jié)果都會不同。交論文前要把隨機種子固定用rng(2020)這類語句固定隨機數(shù)否則答辯時隔壁組問你“為什么重跑一遍數(shù)字變了”場面會很尷尬。4. 易錯題與失分點Matlab求解中最常見的四類翻車現(xiàn)場4.1 資源重量限制與單位換算約束方向弄反錯在哪兒題面給的是“每箱水和每箱食物的重量”很多備賽者在代碼里寫成“總重量 箱數(shù) 箱數(shù)”忘了乘單箱重量。更隱蔽的錯誤是約束方向?qū)懛幢緫?yīng)是“箱數(shù)乘以單箱重量不超過負重上限”寫成“負重上限不超過箱數(shù)乘以單箱重量”。為什么錯這類題目讀題時停留在文字層面沒有把“箱”和“公斤”分離。代碼里一旦寫反初始購買策略會多買一兩箱物資然后在中途某個沙暴天突然發(fā)現(xiàn)負重超限程序直接終止。動態(tài)規(guī)劃里這種錯誤會表現(xiàn)為“所有路徑都不可行”讓你誤以為是算法問題查半天查不到。怎么答對寫代碼前先畫一張很小的表把“箱數(shù)、單箱重量、總重量、負重上限”四列列出來手動算一組數(shù)據(jù)再對照代碼運行結(jié)果。正確約束的寫法永遠是sum(waterBox * weightWater, foodBox * weightFood) capacity方向別反過來。養(yǎng)成這個習慣之后單位換算類錯誤基本可以消滅。4.2 天氣消耗表背錯高溫和沙暴的規(guī)則顛倒錯在哪兒把“高溫天行走消耗雙倍”記成“沙暴天行走消耗雙倍”或者把“沙暴天不能移動”理解成“沙暴天移動消耗更大”。這兩種錯誤在代碼里都會導致同一類結(jié)果某些天數(shù)的資源消耗算錯最終資金要么虛高要么直接歸零。為什么錯題目給的天氣表通常是一個矩陣行是天氣、列是行動方式。備賽者手抄進代碼時容易串行抄完又不做校驗。加上天氣在題目里用數(shù)字編號1、2、3分別代表什么不同隊員的理解有時候都不一樣。怎么答對把天氣表做成Matlab常量矩陣寫在代碼最頂部用注釋標明“第1行晴朗、第2行高溫、第3行沙暴第1列停留、第2列行走、第3列挖礦”。每次跑通例數(shù)據(jù)前先手動算一遍“晴朗天行走一格消耗多少水”跟代碼輸出對一遍。這個動作十分鐘就能做完能省下后面兩小時的排錯時間。4.3 反推狀態(tài)覆蓋不全漏掉“原地停留”這個動作錯在哪兒反推動態(tài)規(guī)劃從終點倒推時只考慮“上一天走了一格到達當前位置”漏了“上一天其實是在原地停留”的情況。結(jié)果就是某些狀態(tài)往前推的時候找不到前驅(qū)顯示為狀態(tài)孤島最終最優(yōu)解被卡死。為什么錯人的直覺是“要往前走才會接近終點”于是只推行走分支。但沙暴天不能移動而沙暴前可能在礦山挖礦挖礦期間天天原地不動。這些原地不動不是浪費是賺錢。漏掉它策略集合直接缺了一大塊。怎么答對在反推代碼里對每一個狀態(tài)至少推三個來源方向上一格走過來、原地停留、挖礦結(jié)束。寫偽代碼時先把三個分支都列出來再通過約束條件刪掉不合法分支。這樣即使最后某個分支無效你也能在注釋里解釋“因為負重上限不滿足所以刪除”閱卷人看到的是完整的思維過程而不是結(jié)果碰巧正確。4.4 終局資金口徑剩余物資到底算不算錢錯在哪兒到達終點后背包里還剩水和食物有人直接按購買價折算回資金加進終局資金。也有人把剩余物資直接歸零一分錢不算。兩個口徑跑出來的最優(yōu)解完全不同。為什么錯題目通常只說了“到終點時的資金最大”沒說剩余物資能回收。如果按購買價折算等于是讓題目白送一筆錢如果完全歸零又會低估那些“多買物資求穩(wěn)”策略的價值。兩種口徑都缺一句解釋。怎么答對在論文里明確寫“本文按抵達終點時的現(xiàn)金余額作為目標函數(shù)剩余物資不計入收益”然后在模型部分解釋一句“若考慮物資回收需要在題目給定規(guī)則下另行建模本文按最保守口徑處理”。這樣既守住一致性又展示你注意到了這個口徑問題。代碼里對應(yīng)把終局狀態(tài)的資金直接取出來不要在最后一步偷偷加回收價。5. 拿分技巧與押題思路先跑通、再調(diào)優(yōu)、最后寫論文5.1 編程題的時間分配與驗證順序現(xiàn)場最常見的悲劇是前半天沉浸在建模型后半天發(fā)現(xiàn)代碼跑不通論文只能編結(jié)果。拿分的關(guān)鍵不是模型多漂亮而是“按時交出可信結(jié)果”。推薦的時間分配是前五分之一時間只做一件事把狀態(tài)變量和約束手寫在紙上不碰代碼。然后花五分之二時間寫一版能跑起來的貪心或枚舉先保證有結(jié)果再花五分之二時間去優(yōu)化。最后留出一些時間整理圖表和摘要。驗證順序也很重要。拿到結(jié)果別急著畫圖先做三個檢查第一個檢查是單位維數(shù)對不對看看輸出資金量級和手工估算是否接近第二個檢查是對比隨機策略用rand亂走500次取平均值看看你的算法結(jié)果是否明顯高于隨機第三個檢查是改寫一個參數(shù)比如把沙暴概率調(diào)成0看結(jié)果是否回落為第一問結(jié)果。這三個檢查全過結(jié)果基本可信寫進論文心里不虛。5.2 論文結(jié)果呈現(xiàn)一張結(jié)果表、三張曲線圖、一段靈敏度分析閱卷人在每份論文上停留的時間有限結(jié)果呈現(xiàn)越直接越容易被看見。按這個結(jié)構(gòu)準備開頭放一張“不同方案終局資金對比表”方案包括枚舉、貪心、動態(tài)規(guī)劃/搜索中間放三張曲線圖分別是“資源存量隨時間變化”“資金隨時間變化”“沙暴概率對最優(yōu)購買量的影響”最后用一段靈敏度分析解釋參數(shù)變化比如水價上漲10%最優(yōu)購水量怎么變。寫論文時不要堆代碼截圖只放“算法流程”和“核心公式”。Matlab源代碼可以在附錄給出但要保證代碼能直接運行注釋里寫明每段參數(shù)的含義。有些隊伍把附錄寫成一堆變量名沒有任何注釋的裸代碼這等于主動放棄回收分。從那以后我每次拿到B題類任務(wù)第一件事都不是打開Matlab而是先手寫狀態(tài)變量表把“天數(shù)、位置、資源、資金”四個維度列全確認約束方向再碰鍵盤。這個習慣幫我躲過了大多數(shù)狀態(tài)爆炸和單位換算的坑也讓我在代碼跑不通時知道該往哪兒看。步驟看似多花十分鐘省下的卻是整場排錯時間。希望幫到你。本文還有配套的精品資源點擊獲取