数学建模实战:基于混合整数规划的家庭旅游套餐优化设计 📅 发布时间:2026/8/21 7:46:02 👁 浏览次数: 1. 项目概述从数学建模到旅游套餐的实战跨越最近刚带完一个数学建模的校队正好有学生问起关于“旅游套餐设计”这类优化问题的实战思路。这让我想起了Mathorcup妈妈杯数学建模竞赛里一道非常经典的题目——家庭暑假旅游套餐设计。这道题本质上是一个典型的资源分配与多目标优化问题但它巧妙地将冰冷的数学模型与充满烟火气的家庭旅游规划结合在了一起。对于刚接触建模的新手来说这是个绝佳的练手项目因为它场景具体、目标明确同时又涵盖了线性规划、整数规划、多目标决策等核心建模思想。而对于有经验的从业者比如旅游行业的产品经理或数据分析师这道题的底层逻辑——如何在有限的预算、时间和偏好约束下组合出满意度最高的产品——正是日常工作的核心。简单来说这道题要求你扮演一个旅游规划师为一个典型的三口之家父母带一个孩子设计一份暑假7天的旅游套餐。你需要综合考虑交通、住宿、景点门票、餐饮等各项费用同时还要平衡游玩体验、休闲程度、教育意义等多个“软性”指标。最终你需要输出一份详细的行程计划表、预算分配方案并用一个量化的“综合满意度”分数来评价你的方案。题目通常会提供一些基础数据比如不同城市间的交通方式与价格、各类酒店的档次与费用、景点的评分与开放时间等。你的任务就是利用这些数据通过数学建模的方法找出一条“最优”的旅游路径。这听起来是不是很像我们自己在做旅行攻略没错数学建模的魅力就在于它能把我们生活中这种凭感觉、靠经验的决策过程抽象成一套可计算、可优化的科学方法。接下来我就结合自己多年打比赛和做项目的经验把这道题的解题思路、模型构建、MATLAB实现以及那些容易踩坑的细节掰开揉碎了讲给你听。无论你是备战数模竞赛的学生还是对运筹优化感兴趣的朋友相信这篇长文都能给你带来实实在在的收获。2. 问题拆解与核心思路把旅行计划变成数学模型面对“家庭暑假旅游套餐设计”这样一个开放式问题第一步也是最关键的一步就是进行问题拆解。你不能一头扎进代码里必须先想清楚到底要优化什么约束条件有哪些数据从哪里来模型怎么选2.1 明确优化目标满意度如何量化这是整个建模的灵魂。题目要求“综合满意度最高”但“满意度”是个主观感受。我们的任务就是把它客观化、量化。通常可以从以下几个维度构建评价体系经济性总花费越低经济性满意度越高。但这并非单纯追求最低价而是追求性价比。游玩体验景点的质量、多样性自然风光、历史人文、主题乐园等。舒适度住宿的档次、交通的便捷性与舒适性如高铁优于长途大巴。劳逸结合行程不能太紧凑每天要有合理的休息和自由活动时间。家庭特色是否包含适合孩子的教育性或娱乐性景点是否兼顾父母的兴趣。量化方法示例经济性可以用预算的节省比例来评分。例如设定一个预算上限B_max实际花费为C则经济性评分 S_cost (B_max - C) / B_max * 权重。如果花费超过预算此项得分为负。游玩体验可以预先给每个景点一个综合评分如1-5分行程中所有景点评分的加权和作为体验分。舒适度为不同等级的交通和住宿赋予基础分进行累加。劳逸结合计算每天安排的景点数量数量在一个合理区间如2-3个得分高过多或过少得分低。家庭特色判断行程中是否包含至少一个高评分儿童景点和一个父母可能感兴趣的景点如博物馆、古镇。最终综合满意度F可以定义为这些维度得分的加权和F w1*S_cost w2*S_experience w3*S_comfort w4*S_balance w5*S_family。权重的设定体现了家庭的偏好例如更看重体验还是更看重省钱。注意权重的设定没有绝对标准是模型中的一个“超参数”。在解题时可以假设一组合理的权重如游玩体验0.4经济性0.3舒适度0.2其他0.1并在敏感性分析中探讨权重变化对结果的影响。这是体现你思考深度的地方。2.2 识别核心约束条件现实世界的条条框框模型不能天马行空必须遵守现实规则这些就是约束条件时间约束总行程固定为7天6晚。每天的有效游玩时间有限如8小时景点有参观所需时间交通有耗时。空间衔接约束行程必须连贯。第n天晚上的住宿地点必须与第n1天的起始游玩地点在同一城市或相邻区域。跨城市移动需要耗费时间和交通成本。预算约束总花费交通住宿门票餐饮其他不能超过家庭给定的预算上限。逻辑约束一个景点最多参观一次。某些景点可能存在互斥关系如两个同质化的古镇或依赖关系如B景点只在参观A景点后的下午开放。住宿必须提前预订通常假定每晚住一个酒店。家庭偏好约束可选但建议体现例如孩子年龄小则行程中不宜安排过多长途车程父母体力一般则爬山类景点不宜过多。2.3 模型选择为什么是混合整数线性规划拆解完目标和约束我们发现这个问题包含两种决策连续决策在每个景点游玩的时间比例、在某种交通方式上的花费等。离散决策是否选择某个景点是/否、是否选择某条交通路线是/否、选择哪家酒店多选一。这种同时包含连续变量和整数0-1变量的问题最经典的模型就是混合整数线性规划。MILP的求解器如MATLAB的intlinprog或更专业的Gurobi、CPLEX已经非常成熟能够有效处理这类组合优化问题。基本建模思路定义0-1决策变量x_ij 1表示从地点i直接前往地点j交通弧的选择y_k 1表示选择景点kz_m 1表示选择第m家酒店。定义连续决策变量t_k表示在景点k游玩的时长cost_transcost_hotel分别表示交通和住宿的具体花费。建立目标函数将前面量化的综合满意度F表达为上述决策变量的线性函数。由于满意度最大化等价于其负值的最小化通常我们将目标设为Minimize: -F。建立约束条件的线性等式或不等式将2.2中所有约束用决策变量和已知参数如时间、价格表示出来。调用求解器求解。这个框架是通用的 backbone。在实际比赛中你需要根据题目给出的具体数据表来具体定义你的变量和参数。3. 数据准备与模型详细构建打造你的“旅游数据库”模型是骨架数据是血肉。题目通常会提供几个Excel或文本格式的数据表我们需要把它们“翻译”成模型能理解的参数。3.1 典型数据表解析与处理假设题目提供了以下四张表你需要根据实际题目调整城市间交通信息表包含城市A、城市B、交通方式飞机/高铁/汽车、票价、耗时、舒适度等级。景点信息表包含景点名称、所在城市、门票价格、建议游玩时长、景点类型自然/人文/游乐、综合评分、适合人群家庭/成人。酒店信息表包含酒店名称、所在城市、档次经济/舒适/豪华、每晚价格、评分。家庭偏好与预算表包含总预算、对景点类型的偏好权重、对交通舒适度的最低要求等。MATLAB数据处理实战% 假设数据保存在 ‘data.xlsx‘ 的不同Sheet中 transport_data readtable(‘data.xlsx‘, ‘Sheet‘, ‘交通‘); scenic_data readtable(‘data.xlsx‘, ‘Sheet‘, ‘景点‘); hotel_data readtable(‘data.xlsx‘, ‘Sheet‘, ‘酒店‘); preference_data readtable(‘data.xlsx‘, ‘Sheet‘, ‘偏好‘); % 提取关键参数向量或矩阵 % 例如构建城市列表 cities unique(transport_data.出发城市); num_cities length(cities); % 构建交通成本矩阵和耗时矩阵三维出发城市 x 到达城市 x 交通方式 % 初始化为大数表示不可直达 INF 1e6; cost_matrix INF * ones(num_cities, num_cities, 3); time_matrix INF * ones(num_cities, num_cities, 3); for i 1:height(transport_data) from_idx find(strcmp(cities, transport_data.出发城市{i})); to_idx find(strcmp(cities, transport_data.到达城市{i})); switch transport_data.交通方式{i} case ‘飞机‘ mode 1; case ‘高铁‘ mode 2; case ‘汽车‘ mode 3; end cost_matrix(from_idx, to_idx, mode) transport_data.票价(i); time_matrix(from_idx, to_idx, mode) transport_data.耗时_小时_(i); end % 类似地处理景点和酒店数据建立索引映射关系 % scenic_list, scenic_city_index, scenic_price, scenic_duration, scenic_score... % hotel_list, hotel_city_index, hotel_price, hotel_score...这一步看似繁琐但至关重要。清晰的索引映射如景点k属于城市c是后续编写约束条件的基础。务必在代码中多加注释并多用assert语句检查数据完整性比如检查是否有景点或酒店所在城市不在城市列表中。3.2 决策变量定义与模型建立接下来我们严格定义MILP模型的所有组成部分。为了清晰我们假设一个简化场景规划一条访问N个候选景点、跨越C个城市的7日行程。决策变量定义% 0-1变量是否选择景点k y optimvar(‘y‘, num_scenics, ‘Type‘, ‘integer‘, ‘LowerBound‘, 0, ‘UpperBound‘, 1); % 0-1变量第d天是否住在城市c的酒店h z optimvar(‘z‘, num_days, num_cities, num_hotels_per_city, ‘Type‘, ‘integer‘, ‘LowerBound‘, 0, ‘UpperBound‘, 1); % 0-1变量第d天是否从城市i通过交通方式m前往城市j x optimvar(‘x‘, num_days, num_cities, num_cities, num_trans_modes, ‘Type‘, ‘integer‘, ‘LowerBound‘, 0, ‘UpperBound‘, 1); % 连续变量第d天在景点k游玩的时长小时 t optimvar(‘t‘, num_days, num_scenics, ‘LowerBound‘, 0); % 连续变量总交通花费、总住宿花费等 cost_trans optimvar(‘cost_trans‘, ‘LowerBound‘, 0); cost_hotel optimvar(‘cost_hotel‘, ‘LowerBound‘, 0);目标函数构建我们需要把3.1中量化的综合满意度F用这些变量表示出来。例如游玩体验分S_experience sum( scenic_score(k) * sum_over_days(t(d,k)) / scenic_duration(k) )这里用游玩时长占比来近似是否充分游玩。经济性评分S_cost (总预算 - (cost_trans cost_hotel ...)) / 总预算。然后加权求和得到F最终问题是Minimize -F。约束条件编写关键部分行程连贯性约束第d天游玩结束后所在城市必须等于第d晚入住酒店所在城市。% 这是一个简化表达实际需要用sum函数聚合变量 % 例如对于每一天d离开城市i的流量等于进入城市j的流量 for d 1:num_days for i 1:num_cities cons sum(sum(x(d,i,:,:), 4), 3) sum(sum(x(d,:,i,:), 4), 2); % 流量平衡 prob.Constraints.(‘flow_balance_‘ string(d) ‘_‘ string(i)) cons; end end时间约束每天交通耗时 景点游玩耗时 基本休息时间 每日最大可用时间如14小时。for d 1:num_days travel_time sum(sum(sum( time_matrix(i,j,m) .* x(d,i,j,m) ))); % 求和计算当天交通总耗时 play_time sum(t(d, :)); % 当天游玩总时长 prob.Constraints.(‘daily_time_‘ string(d)) travel_time play_time 2 14; % 假设休息2小时 end景点访问逻辑约束一个景点最多被访问一次可以玩半天也可以玩一天。for k 1:num_scenics prob.Constraints.(‘scenic_visit_once_‘ string(k)) sum(t(:, k) 0) 1; % 时长大于0的天数最多1天 % 更严格的0-1约束如果景点被选中则必须玩够最小时长 prob.Constraints.(‘scenic_min_time_‘ string(k)) t(d,k) 0.5 * scenic_duration(k) * y(k); % 至少玩建议时长的一半 prob.Constraints.(‘scenic_max_time_‘ string(k)) t(d,k) scenic_duration(k) * y(k); end住宿唯一性约束每晚只能住一家酒店。for d 1:num_days prob.Constraints.(‘one_hotel_per_night_‘ string(d)) sum(sum(z(d, :, :), 3), 2) 1; end预算约束prob.Constraints.budget cost_trans cost_hotel sum(scenic_price .* y) ... total_budget; % 其中 cost_trans sum( sum( sum( sum( cost_matrix(i,j,m) .* x(d,i,j,m) ) ) ) ); % cost_hotel sum( sum( sum( hotel_price(c,h) .* z(d,c,h) ) ) );实操心得约束条件的编写是最容易出错的地方。强烈建议你从一个小规模的、只有2-3个城市、3-4个景点的例子开始手动推演一下约束应该如何写并打印出中间变量来验证。例如先关闭大部分约束只保留预算和几个简单约束看求解器能否给出一个可能不合理的解然后逐步添加约束观察解的变化是否符合预期。这比直接写一个庞大模型然后debug要高效得多。4. MATLAB求解与结果分析让模型“跑”起来并读懂它模型建立好后就到了激动人心的求解阶段。但别急直接求解可能会遇到问题。4.1 求解器配置与技巧MATLAB中求解MILP的核心函数是intlinprog或者使用Problem-Based Optimization上面示例用的就是这种更直观。对于大规模问题后者更方便。% 使用问题式优化方法 prob optimproblem(‘ObjectiveSense‘, ‘maximize‘); % 因为我们想最大化F但intlinprog默认最小化所以目标函数写为 F prob.Objective F; % F是由决策变量构成的表达式 % 指定哪些变量是整数0-1变量 prob.Constraints.y_integer y; % 实际上在定义变量时指定了‘Type‘, ‘integer‘这里主要是整合所有约束 % 求解 [sol, fval, exitflag, output] solve(prob); % 检查求解状态 if exitflag 0 disp(‘求解成功‘); % 提取解 y_sol sol.y; x_sol sol.x; t_sol sol.t; % ... 其他变量 else disp(‘求解失败或未找到最优解。‘); disp(output.message); end关键技巧与注意事项求解时间MILP是NP-Hard问题城市和景点数量稍多比如超过15个求解时间就可能指数级增长。可以设置时间限制。options optimoptions(‘intlinprog‘, ‘MaxTime‘, 300); % 最大300秒 [sol, fval] solve(prob, ‘Options‘, options);初始解提供一个好的初始解可以显著加快求解速度。你可以先用启发式算法如贪婪算法快速生成一个可行解然后将其作为intlinprog的初始点x0。松弛与分解如果问题太大可以考虑时间分解先按天规划再考虑天与天之间的衔接。地理分解先确定要访问的城市集群再规划每个城市内部的行程。拉格朗日松弛将某些复杂约束如流量平衡放到目标函数中将原问题分解为更易求解的子问题。这属于高级技巧在竞赛中能体现建模深度。4.2 结果解析与行程可视化求解成功后sol结构体里包含了所有决策变量的值。我们需要把这些0和1还原成普通人能看懂的行程单。步骤一提取关键决策% 1. 确定哪些景点被选中 selected_scenic_indices find(round(sol.y) 1); % 注意处理浮点误差用round selected_scenics scenic_list(selected_scenic_indices); % 2. 确定每天的住宿 daily_hotel cell(num_days, 1); for d 1:num_days [city_idx, hotel_idx] find(round(squeeze(sol.z(d, :, :))) 1); daily_hotel{d} sprintf(‘第%d晚入住【%s】的【%s】酒店‘, d, cities{city_idx}, hotel_list{hotel_idx}); end % 3. 确定每天的交通 daily_transport cell(num_days, 1); for d 1:num_days for i 1:num_cities for j 1:num_cities for m 1:num_trans_modes if round(sol.x(d, i, j, m)) 1 daily_transport{d} sprintf(‘第%d天从【%s】乘坐【%s】前往【%s】‘, d, cities{i}, transport_mode{m}, cities{j}); break; % 假设一天只有一次主要城际交通 end end end end end % 4. 确定每个景点在哪天玩玩多久 schedule cell(num_days, 1); for d 1:num_days day_play []; for k 1:num_scenics if sol.t(d, k) 0.1 % 游玩时间大于0.1小时 day_play [day_play; sprintf(‘ - %s: 游玩%.1f小时‘, scenic_list{k}, sol.t(d, k))]; end end schedule{d} strjoin(day_play, newline); end步骤二生成可视化报告除了文本图表更能直观展示结果。甘特图展示行程用barh或专门的甘特图函数展示每天的时间分配。figure(‘Position‘, [100, 100, 800, 400]); for d 1:num_days % 计算每天各项活动的时间块 % 略去具体绘图代码核心是使用 barh 或 patch 绘制时间矩形 end xlabel(‘时间 (小时)‘); ylabel(‘天数‘); title(‘家庭暑假旅游行程甘特图‘); legend(‘交通‘, ‘景点A‘, ‘景点B‘, ‘休息‘, ‘Location‘, ‘best‘);预算分配饼图figure; cost_labels {‘交通‘, ‘住宿‘, ‘门票‘, ‘餐饮‘, ‘其他‘}; cost_values [total_trans_cost, total_hotel_cost, total_ticket_cost, total_food_cost, total_other]; pie(cost_values, cost_labels); title(‘旅游套餐预算分配‘);地图轨迹图如果坐标数据用plot或geoplot画出城市间的移动轨迹直观展示旅游路线。步骤三输出最终方案文档将以上所有信息整合生成一个结构清晰的Word或PDF文档至少包含摘要一句话说明方案特色如“主打文化体验与舒适度假的华东七日游”。详细行程表以表格形式列出每天日期、住宿城市、交通方式、上午活动、下午活动、餐饮建议、预计花费。预算明细表分项列出所有费用。方案亮点与满意度分析解释你的方案在哪些维度上得分高为什么这样设计。备选方案或调整建议如果预算增加/减少一天可以如何调整。5. 模型评估、优化与常见问题排坑一个模型做完工作只完成了一半。更重要的是评估它、改进它并知道哪里容易出错。5.1 模型评估你的方案真的好吗可行性检验这是最基本的。手动沿着你的行程走一遍检查时间是否够用城市衔接是否合理例如第一天晚上住北京第二天上午的景点却在上海这显然不行。检查预算是否计算准确。敏感性分析改变关键参数观察结果的变化是否合理。权重敏感性调整满意度目标函数中“经济性”和“游玩体验”的权重看行程是从“穷游”平滑过渡到“品质游”还是发生了突变突变点意味着什么预算敏感性将总预算提高10%或降低10%新的方案是增加了更多景点还是升级了交通住宿这能帮你理解方案的弹性。时间敏感性如果某个景点建议游玩时间不准确你的方案鲁棒性如何与基准方案对比可以设计一个简单的“基准方案”比如只去评分最高的三个景点住最便宜的酒店选最便宜的交通。然后对比你的优化方案在满意度各项指标上提升了多少用数据说话。5.2 模型优化与进阶思路基础模型跑通后可以考虑以下优化让你的方案更出彩引入随机性或模糊性现实中有很多不确定性。比如交通可能晚点景点排队时间可能波动。你可以引入随机规划或鲁棒优化的思想。例如将景点游玩时间设为一个区间[t_min, t_max]要求在任何可能的时间实现下行程都可行鲁棒优化或者追求期望满意度最高随机规划。动态定价与优惠考虑机票/酒店提前预订的折扣或者团购门票的优惠。这需要引入更复杂的、与时间相关的成本函数。多家庭或团队规划题目是单个家庭你可以思考如何扩展到多个家庭如两个好友家庭一起出游他们可能有共同的景点也可能有不同的偏好需要协调时间和路线。这可以引申为更复杂的多智能体优化问题。集成机器学习预测用历史数据训练模型预测景点的拥挤程度影响游玩体验和实际耗时、预测未来的机票价格波动等将这些预测结果作为你优化模型的输入。5.3 常见问题与排查实录在实现过程中你几乎一定会遇到下面这些问题问题一模型求解时间过长甚至无法得到可行解。可能原因1问题规模太大。排查检查你的决策变量数量。城市数景点数天数很容易导致变量爆炸。解决预处理减少变量合并相邻且类型相似的小景点为一个“景点区”剔除明显不符合家庭偏好或评分过低的景点将同城市内交通视为固定耗时和成本不建模为决策变量。分解问题采用“先定城市再定景点”的两阶段法。第一阶段用简化模型只考虑城市和主要交通确定访问哪些城市及顺序第二阶段在每个城市内部详细规划景点。使用启发式算法求近似解当精确求解器intlinprog无能为力时可以转向遗传算法GA、模拟退火SA等。MATLAB的全局优化工具箱提供了ga函数。虽然不能保证最优但在有限时间内能得到质量很高的可行解这在竞赛中是完全可接受的。% 遗传算法示例框架需将问题转化为适应度函数 fitnessfcn (x) myTravelFitness(x, data); % x是编码后的决策变量 nvars length_of_encoded_x; [x_ga, fval_ga] ga(fitnessfcn, nvars, [], [], [], [], lb, ub, myTravelConstraints);可能原因2约束条件太紧或互相矛盾导致无解。排查逐步放松约束。例如先去掉“每天游玩时间不超过8小时”的约束看是否有解。如果有再逐步收紧找到导致无解的关键约束。解决检查约束条件的数学表达是否正确。特别是那些涉及多个变量求和的约束很容易写错索引。打印出约束矩阵的某几行人工检查。或者尝试求模型的松弛问题去掉整数约束如果松弛问题都无解那原问题肯定无解说明约束本身存在矛盾。问题二求解得到的行程不合理比如一天之内在两个相距很远的城市间来回。可能原因缺少“消除子回路”的约束。这是旅行商问题中的经典约束。在我们的问题中虽然不一定是严格的TSP但类似的你需要防止行程中出现不合理的跳跃。解决引入MTZ约束或DFJ约束。对于MILP模型可以添加一个辅助变量u_i表示访问城市i的顺序然后添加约束u_i - u_j n * x_ij n-1。这会强制形成一条连贯的路径而不是多个不相连的环。问题三目标函数值看起来没问题但提取出的具体行程无法解释。可能原因决策变量之间的关联约束没写好。例如y(k)1选择景点k和t(d,k)0在某天游玩景点k之间的逻辑关系没约束好可能导致模型“选择”了景点但没分配游玩时间或者分配了时间但没“选择”该景点。解决仔细检查所有连接0-1变量和连续变量的约束。确保它们是“激活”约束。常用的技巧是使用“大M法”。例如t(d,k) M * y(k) 其中M是一个很大的数如每天最大时间24。这保证了只有当y(k)1时t(d,k)才可以大于0当y(k)0时t(d,k)必须为0。问题四如何将抽象的“家庭偏好”融入模型解决这是体现建模技巧的地方。例如“孩子喜欢游乐场”在目标函数中体现提高游乐场类景点的评分权重。作为硬约束强制要求行程中至少包含N个游乐场景点。sum(y(k) for k in 游乐场集合) N。作为软约束不强制要求但如果包含了则在目标函数中获得额外奖励分。 通常将核心偏好作为硬约束将次要偏好通过权重融入目标函数是一个平衡解的质量和可行性的好方法。最后记住数学建模竞赛的核心是“建模”而不是“编程”。你的论文需要清晰地阐述模型假设、符号说明、建模过程、求解方法、结果分析和模型评价。MATLAB代码是工具是验证你模型可行性的手段。将上述所有思考过程包括你遇到的坑和解决方案有条理地写在论文里才能获得评委的青睐。希望这篇超详细的拆解能帮你不仅搞定这道题更掌握解决一类优化问题的核心思想。