问题2:
建模思路:针对问题2,天气仅当天可知,需当天动态决策,结合场景树法覆盖“天气不确定性”的建模思路,核心围绕随机规划( Stochastic Programming )适配动态天气场景的逻辑展开,,将“动态决策”转化为“多场景下的滚动优化”。
建模准备:
与第一问的衔接
●复用基础模型:第一问的“确定性MIP模型”(路径、资源、采矿/采购逻辑)直接作为单场景求解器。
●扩展动态逻辑:通过场景树“并行求解多天气可能”,再聚合结果得到动态环境下的鲁棒策略。
关键说明
●场景数量:需平衡“计算量”与“场景代表性”,用蒙特卡洛抽样保证覆盖主要天气风险。
●动态反馈:实际游戏中,当日天气已知后,可“截断场景树,仅保留已发生天气的分支”滚动优化(更贴近真实决策)。
建模过程:场景树法适配建模步骤
1.场景生成:覆盖天气不确定性
●定义天气维度:天气为“晴朗、高温、 沙暴”,设总天数为T,每日天气是随机变量[数学公式]
●生成场景序列:用蒙特卡洛模拟生成N条天气场景序列[数学公式],每条[数学公式]代表一种“从第1天到第T天’的天气可能。
2.动态决策与模型复用
●决策分层:将每日行动(移动/停留/采矿/采购)拆解为“阶段决策”,第t天的决策 [数学公式]依赖:
当日天气[数学公式] (已知时动态调整);
前t- 1天的资源/资金状态(路径依赖)。
●复用MIP框架:对每条场景 [数学公式],将“动态天气”代入第一问的确定性MIP模型(资源约束、移动规则、采矿/采购逻辑不变),求解该场景下的最优路径与决策序列 [数学公式]
3.策略聚合:鲁棒性与期望优化
保守策略:筛选所有场景中“资源永不崩盘、必达终点”的行动集合,保证极端天气下存活。
目标:最大化“最坏场景下的剩余资金”
●期望最优:计算各场景决策的平均最终资金,目标:选期望最大的方案。
●鲁棒优化:找“最坏天气场景(如连续沙暴)”中表现最好的决策(Min-Max思想)。
4.模型数学表达(简化版)
以“资源约束+场景适配”为核心,构建随机规划模型:
[数学公式]St [数学公式]
E[[数学公式]]:对N个场景的期望;
[数学公式]:场景n第t天的资源(水/食物);
C[数学公式]:天气[数学公式]下行动[数学公式]的资源消耗;
S[数学公式]:行动[数学公式]的资源补充(如村庄采购、矿山不采矿时无补充)。
目标函数:
① 期望最优(最大化平均剩余资金)(用问题1的MIP模型求解单场景最优):
[数学公式]
② 鲁棒最优(最坏场景下资金最大):
[数学公式]import numpy as np
import pandas as pd
from tabulate import tabulate
import random
import math
from collections import defaultdict, deque
import heapq
import networkx as nx
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
# 基础参数设置
WEATHER_TYPES = ['晴朗', '高温'] # 第三关已知不会出现沙暴天气
WEATHER_PROBS = [0.5, 0.5] # 假设晴朗和高温概率相等
SCENARIO_COUNT = 50 # 场景数量
MAX_DAYS = 10 # 最大天数
START_MONEY = 10000 # 初始资金
MAX_WEIGHT = 1200 # 最大负重
WATER_PRICE = 5 # 水单价
FOOD_PRICE = 10 # 食物单价
WATER_WEIGHT = 3 # 水的单位重量
FOOD_WEIGHT = 2 # 食物单位重量
# 资源消耗量(按天气类型)
WEATHER_CONSUMPTION = {
'晴朗': {'water': 3, 'food': 4},
'高温': {'water': 9, 'food': 9}
}
# 地图参数
n = 13
adj_matrix = [[0 for _ in range(n+1)] for _ in range(n+1)]
edges = [(1,2),(1,4),(1,5),(2,3),(2,4),(3,4),(3,8),(3,9),(4,5),(4,6),(4,7),
(5,6),(6,7),(6,12),(6,13),(7,12),(7,11),(8,9),(9,10),(9,11),
(10,11),(10,13),(11,12),(11,13),(12,13)]
for u, v in edges:
adj_matrix[u][v] = 1
adj_matrix[v][u] = 1
# 节点类型定义
NODE_TYPES = {
'NORMAL': 0,
'START': 1, # 起点
'MINE': 2, # 矿山
'VILLAGE': 3, # 村庄
'END': 4 # 终点
}
# 设置特殊节点
node_types = [NODE_TYPES['NORMAL']] * (n + 1)
node_types[1] = NODE_TYPES['START'] # 起点
node_types[9] = NODE_TYPES['MINE'] # 矿山
node_types[13] = NODE_TYPES['END'] # 终点
# 生成天气场景
def generate_weather_scenarios(days, count):
"""生成指定天数和数量的天气场景"""
scenarios = []
for _ in range(count):
scenario = random.choices(WEATHER_TYPES, weights=WEATHER_PROBS, k=days)
scenarios.append(scenario)
return scenarios
# 创建基础图
def create_graph():
G = nx.Graph()
for u, v in edges:
G.add_edge(u, v)
return G
# 确定性MIP模型求解器(改进版)
def solve_deterministic_model(G, weather_sequence, start=1, end=13):
"""求解确定性模型,返回最优路径和收益"""
best_result = None
# 使用队列进行BFS搜索
# 状态包括: (当前位置, 路径, 水, 食物, 资金, 天数, 采矿天数)
queue = deque()
visited = set()
# 初始购买策略(在起点购买资源)
max_water = min(MAX_WEIGHT // WATER_WEIGHT, START_MONEY // WATER_PRICE)
max_food = min(MAX_WEIGHT // FOOD_WEIGHT, START_MONEY // FOOD_PRICE)
# 尝试不同的初始购买组合
for water in range(0, max_water + 1, 5): # 步长为5以减少状态空间
for food in range(0, max_food + 1, 5):
cost = water * WATER_PRICE + food * FOOD_PRICE
if cost > START_MONEY:
continue
if water * WATER_WEIGHT + food * FOOD_WEIGHT > MAX_WEIGHT:
continue
money = START_MONEY - cost
state_key = (start, 0, water, food, money)
if state_key in visited:
continue
visited.add(state_key)
queue.append((start, [start], water, food, money, 0, []))
while queue:
current, path, water, food, money, day, mining_days = queue.popleft()
# 如果到达终点
if current == end:
# 终点退回剩余资源
refund = water * WATER_PRICE / 2 + food * FOOD_PRICE / 2
money += refund
if best_result is None or money > best_result[2]:
best_result = (path, mining_days, money)
continue
# 如果超过最大天数
if day >= len(weather_sequence):
continue
# 获取当前天气
weather = weather_sequence[day]
# 计算基础消耗
water_consumption = WEATHER_CONSUMPTION[weather]['water']
food_consumption = WEATHER_CONSUMPTION[weather]['food']
# 尝试所有可能的行动
# 1. 在当前区域停留(仅在村庄或矿山有意义)
if node_types[current] == NODE_TYPES['VILLAGE'] or node_types[current] == NODE_TYPES['MINE']:
if water >= water_consumption and food >= food_consumption:
new_water = water - water_consumption
new_food = food - food_consumption
# 在村庄停留时可以购买资源
if node_types[current] == NODE_TYPES['VILLAGE']:
# 计算最大可购买量
max_water_buy = min((MAX_WEIGHT - (new_food * FOOD_WEIGHT)) // WATER_WEIGHT,
money // WATER_PRICE)
max_food_buy = min((MAX_WEIGHT - (new_water * WATER_WEIGHT)) // FOOD_WEIGHT,
money // FOOD_PRICE)
# 尝试购买资源(步长为5)
for buy_water in range(0, max_water_buy + 1, 5):
for buy_food in range(0, max_food_buy + 1, 5):
cost = buy_water * WATER_PRICE + buy_food * FOOD_PRICE
if money < cost:
continue
new_water2 = new_water + buy_water
new_food2 = new_food + buy_food
new_money = money - cost
# 检查负重
weight = new_water2 * WATER_WEIGHT + new_food2 * FOOD_WEIGHT
if weight > MAX_WEIGHT:
continue
# 创建新状态
state_key = (current, day+1, new_water2, new_food2, new_money)
if state_key not in visited:
visited.add(state_key)
new_path = path.copy() # 停留不改变路径
queue.append((current, new_path, new_water2, new_food2, new_money, day+1, mining_days.copy()))
else:
# 矿山停留但不采矿
state_key = (current, day+1, new_water, new_food, money)
if state_key not in visited:
visited.add(state_key)
new_path = path.copy() # 停留不改变路径
queue.append((current, new_path, new_water, new_food, money, day+1, mining_days.copy()))
# 2. 移动到相邻区域
for neighbor in G.neighbors(current):
# 检查资源是否足够一天消耗
if water >= water_consumption and food >= food_consumption:
new_water = water - water_consumption
new_food = food - food_consumption
new_money = money
# 在村庄购买资源(到达时立即购买)
if node_types[neighbor] == NODE_TYPES['VILLAGE']:
# 计算最大可购买量
max_water_buy = min((MAX_WEIGHT - (new_food * FOOD_WEIGHT)) // WATER_WEIGHT,
new_money // WATER_PRICE)
max_food_buy = min((MAX_WEIGHT - (new_water * WATER_WEIGHT)) // FOOD_WEIGHT,
new_money // FOOD_PRICE)
# 尝试购买资源(步长为5)
for buy_water in range(0, max_water_buy + 1, 5):
for buy_food in range(0, max_food_buy + 1, 5):
cost = buy_water * WATER_PRICE + buy_food * FOOD_PRICE
if new_money < cost:
continue
new_water2 = new_water + buy_water
new_food2 = new_food + buy_food
new_money2 = new_money - cost
# 检查负重
weight = new_water2 * WATER_WEIGHT + new_food2 * FOOD_WEIGHT
if weight > MAX_WEIGHT:
continue
# 创建新状态
state_key = (neighbor, day+1, new_water2, new_food2, new_money2)
if state_key not in visited:
visited.add(state_key)
new_path = path + [neighbor]
new_mining_days = mining_days.copy()
queue.append((neighbor, new_path, new_water2, new_food2, new_money2, day+1, new_mining_days))
else:
# 非村庄节点
state_key = (neighbor, day+1, new_water, new_food, new_money)
if state_key not in visited:
visited.add(state_key)
new_path = path + [neighbor]
new_mining_days = mining_days.copy()
queue.append((neighbor, new_path, new_water, new_food, new_money, day+1, new_mining_days))
# 3. 在矿山采矿(如果当前在矿山)
if node_types[current] == NODE_TYPES['MINE'] and day < len(weather_sequence) - 1:
# 采矿需要两天时间,消耗两天的资源
total_water = water_consumption * 2
total_food = food_consumption * 2
if water >= total_water and food >= total_food:
new_water = water - total_water
new_food = food - total_food
new_money = money + 1000 # 采矿收益
# 标记采矿天数
new_mining_days = mining_days.copy()
new_mining_days.append(day + 1) # 记录第二天采矿
# 创建新状态
state_key = (current, day+2, new_water, new_food, new_money)
if state_key not in visited:
visited.add(state_key)
new_path = path.copy() # 采矿不改变位置
queue.append((current, new_path, new_water, new_food, new_money, day+2, new_mining_days))
return best_result if best_result else (None, None, 0)
# 场景评估函数
def evaluate_scenarios(G, scenarios, start=1, end=13):
"""评估所有场景,返回每个场景的最优解"""
scenario_results = []
for i, scenario in enumerate(scenarios):
print(f"处理场景 {i+1}/{len(scenarios)}")
# 求解该场景的最优策略
path, mining_days, final_money = solve_deterministic_model(G, scenario, start, end)
# 计算总行程天数
days = len(scenario)
# 计算路径长度
path_length = len(path) - 1 if path else float('inf')
# 记录结果 - 完整输出天气序列和路径
scenario_results.append({
'scenario_id': i+1,
'weather_sequence': '→'.join(scenario), # 完整天气序列
'path': '→'.join(map(str, path)) if path else '无路径', # 完整路径
'path_length': path_length,
'mining_days': '→'.join(map(str, mining_days)) if mining_days else '无采矿',
'total_days': days,
'final_money': final_money
})
return scenario_results
# 策略聚合函数
def aggregate_strategies(results):
"""聚合场景结果,生成不同策略"""
# 转换为DataFrame
df = pd.DataFrame(results)
# 过滤无效结果
valid_df = df[df['final_money'] > 0]
if len(valid_df) == 0:
return None
# 1. 期望最优策略(平均资金最高)
expected_optimal = valid_df.loc[valid_df['final_money'].idxmax()]
# 2. 最坏情况最优(最差场景中表现最好)
worst_case_optimal = valid_df.sort_values('final_money').iloc[0]
# 3. 鲁棒策略(最差20%场景中表现最好的)
robust_df = valid_df.sort_values('final_money').iloc[:max(1, len(valid_df)//5)]
robust_strategy = robust_df.sort_values('final_money').iloc[-1]
return {
'expected_optimal': expected_optimal,
'worst_case_optimal': worst_case_optimal,
'robust_strategy': robust_strategy
}
# 结果展示函数
def display_results(results, strategies):
"""显示结果表格"""
# 转换为DataFrame
df = pd.DataFrame(results)
# 打印统计信息
print("\n=== 场景统计信息 ===")
valid_count = len(df[df['final_money'] > 0])
print(f"有效场景数: {valid_count}/{len(df)}")
if valid_count > 0:
valid_df = df[df['final_money'] > 0]
print(f"平均剩余资金: {valid_df['final_money'].mean():.2f} 元")
print(f"最低剩余资金: {valid_df['final_money'].min()} 元")
print(f"最高剩余资金: {valid_df['final_money'].max()} 元")
print(f"平均路径长度: {valid_df['path_length'].mean():.2f} 步")
# 打印策略对比
if strategies:
print("\n=== 策略对比 ===")
strategy_table = []
for strategy_name, row in strategies.items():
strategy_table.append([
strategy_name,
row['final_money'],
row['path'],
row['mining_days'],
row['total_days']
])
print(tabulate(strategy_table, headers=[
'策略类型', '剩余资金(元)', '路径', '采矿天数', '总天数'
], tablefmt='grid'))
# 保存为Excel
output_file = "第三关结果.xlsx"
df.to_excel(output_file, index=False)
print(f"\n所有场景结果已保存至 '{output_file}'")
# 主函数
def main():
print("=== 第三关建模开始 ===")
# 创建图
G = create_graph()
# 生成天气场景
print(f"生成 {SCENARIO_COUNT} 个天气场景...")
scenarios = generate_weather_scenarios(MAX_DAYS, SCENARIO_COUNT)
# 评估所有场景
print("评估所有场景...")
scenario_results = evaluate_scenarios(G, scenarios)
# 聚合策略
print("聚合策略...")
strategies = aggregate_strategies(scenario_results)
# 显示结果
display_results(scenario_results, strategies)
print("\n=== 建模完成 ===")
if __name__ == "__main__":
main()