简介:本资源为2026年第十九届认证杯数学建模网络挑战赛D题「共享充电宝的投放配置」的完整题解方案,面向参赛队伍及需要学习设施选址与需求建模的读者。包内提供Python全链路代码,涵盖时空潮汐需求提取、空间引力重分配、选址-容量联合规划启发式求解与运营评估;配套近两万字Word论文,完整呈现泊松分布需求建模、空间重力模型距离衰减推演、融合排队论的MINLP模型构建及预算约束下的选址与库存优化,图表精美、格式符合竞赛规范。资源共295个文件,以pdf、rar、zip、doc为主,另有csv结果表、py脚本与xls数据,压缩包约430.53MB,目录清晰便于检索。已有116人学习下载,可帮助读者快速复现实验、对照论文思路并冲刺国奖。
1. 共享充电宝投放配置:这道认证杯 D 题到底在考什么
共享充电宝的投放配置,说白了就是给你一批商场、地铁站、医院、高校的点位数据,让你决定每个点位放几台机柜、每台机柜配多少个充电宝,在满足借还需求的前提下把成本压到最低、把收益拉到最高。2026 年第十九届认证杯数学建模网络挑战赛 D 题就落在这个场景上,它不像纯优化题那样只给一个目标函数,而是把需求预测、点位评分、库存周转、投放成本揉在一起,逼你做一个多目标决策模型。适合谁?正在准备认证杯、华为杯数学建模大赛,或者研究生数学建模这类赛事的同学,尤其是想拿一套能跑通、能改参数、能直接写进论文的完整方案的人。这道题真正的难点不在算法多高级,而在你怎么把「共享充电宝」这个业务逻辑翻译成数学约束——租借率、归还率、机柜容量、补货周期,每一个都是坑。
2. 从业务到模型:需求预测与点位评分怎么落地
2.1 为什么先做需求预测而不是直接上优化
很多人拿到题第一反应是建一个整数规划,把点位、机柜数、充电宝数当决策变量,目标函数写收益减成本,然后丢给求解器。这么做不是不行,但你会发现约束根本写不完整,因为「每个点位需要多少充电宝」这个量你压根没算出来。共享充电宝的核心是周转:一个充电宝被借走、用完、归还,才能再次被借。所以需求预测要预测的不是「有多少人想借」,而是「单位时间内该点位的净借出量」,也就是借出减去归还。
常见做法是按时段做时间序列分解。商场点位有明显的午高峰和晚高峰,地铁站是早晚双峰,医院相对平缓但全天在线,高校则是课间和晚间集中。我一般会把一天切成 24 个时段,对每个点位用历史借还数据算净流量,再用移动平均或 Holt-Winters 做短期预测。如果题目没给历史数据,就用点位属性做回归:人流量、周边业态、是否临街、营业时长这几个特征足够撑起一个线性或树模型。
import numpy as np import pandas as pd # 假设 raw 是借还流水,字段:point_id, hour, borrow, return def net_flow(raw): g = raw.groupby(['point_id', 'hour']).agg( borrow=('borrow', 'sum'), return_=('return', 'sum') ).reset_index() g['net'] = g['borrow'] - g['return_'] # 净借出,正数表示该时段缺口 return g # 对每个点位做 24 小时净流量平滑,避免单日异常 def smooth_net(g, window=3): g = g.sort_values(['point_id', 'hour']) g['net_smooth'] = g.groupby('point_id')['net'].transform( lambda s: s.rolling(window, center=True, min_periods=1).mean() ) return g这段代码的逻辑是先按点位和小时聚合借还量,算出净借出,再做中心移动平均。window=3表示用前后各一小时加当前小时平滑,适合高峰时段波动大的点位。如果你的数据粒度是 15 分钟,window 要相应调大,否则平滑没效果。注意min_periods=1保证边界小时不会变成 NaN,否则后面优化会缺约束。
2.2 点位评分:把「值不值得投」变成一个可比较的数
需求预测出来的是量,但投放还要看这个点位值不值得放。同样是净借出 50 次,放在租金 8000 的商场和租金 2000 的社区便利店,结论完全不一样。所以第二步是点位评分,把租金、人流量、竞争密度、客单价、合作难度这些因素归一化后加权。
我一般用熵权法加专家打分做组合权重,因为纯熵权法会被数据本身的离散度带偏,纯专家打分又太主观。具体做法:先对每个指标做 min-max 归一化,正向指标(人流量、客单价)越大越好,负向指标(租金、竞争密度)越小越好;然后算熵值和差异系数,得到客观权重;再和一组人工权重按 0.6/0.4 融合。最后每个点位得到一个 0 到 1 的评分,作为优化模型里「是否投放」的优先级系数。
| 指标 | 方向 | 归一化方法 | 说明 |
|---|---|---|---|
| 日均人流量 | 正向 | min-max | 反映潜在借还需求 |
| 月租金 | 负向 | min-max 后取反 | 成本核心项 |
| 周边竞品数 | 负向 | min-max 后取反 | 竞争分流 |
| 客单价 | 正向 | min-max | 影响收益上限 |
| 合作难度 | 负向 | 1-5 打分归一 | 进场门槛 |
这张表可以直接放进论文的指标说明部分。注意合作难度这种定性指标,别硬编数据,用 1 到 5 打分再归一化,答辩时说得清来源就行。
3. 投放配置优化模型:整数规划怎么写才不翻车
3.1 决策变量与目标函数的设定
到了核心优化这一步,决策变量一般设三个层次:点位是否投放(0-1 变量)、该点位放几台机柜(整数)、每台机柜配几个充电宝(整数)。目标函数是总利润最大化,等于收益减投放成本减运营成本。收益来自租借次数乘单次价格,租借次数受限于充电宝数量和周转率;成本包括机柜固定成本、充电宝采购成本、租金、补货人工。
这里有个容易翻车的地方:很多人把租借次数直接写成需求预测值,但需求不一定被满足。如果充电宝不够,实际租借次数是 min(需求, 可用充电宝数 × 周转次数)。这个 min 是非线性的,直接丢给线性求解器会报错。常见做法是引入辅助变量和 big-M 约束把它线性化,或者干脆用启发式算法(遗传算法、模拟退火)绕开。
from pulp import LpProblem, LpVariable, LpMaximize, lpSum, LpBinary, LpInteger # 参数示例 points = ['P1', 'P2', 'P3'] demand = {'P1': 120, 'P2': 80, 'P3': 200} # 日净需求 price = 3 # 单次租借价格 cab_cost = 2000 # 单台机柜月成本 pb_cost = 80 # 单个充电宝成本 rent = {'P1': 5000, 'P2': 3000, 'P3': 8000} # 月租金 turnover = 4 # 单个充电宝日周转次数 prob = LpProblem('shared_power_bank', LpMaximize) open_ = {p: LpVariable(f'open_{p}', cat=LpBinary) for p in points} cab = {p: LpVariable(f'cab_{p}', lowBound=0, cat=LpInteger) for p in points} pb = {p: LpVariable(f'pb_{p}', lowBound=0, cat=LpInteger) for p in points} # 目标:收益 - 机柜成本 - 充电宝成本 - 租金 prob += lpSum( price * demand[p] * open_[p] - cab_cost * cab[p] - pb_cost * pb[p] - rent[p] * open_[p] for p in points ) # 约束:充电宝数量不超过机柜容量(假设每柜 12 个) for p in points: prob += pb[p] <= 12 * cab[p] prob += cab[p] <= 10 * open_[p] # 不投放则机柜为 0 prob += pb[p] * turnover >= demand[p] * open_[p] # 满足需求 prob.solve()这段代码用 PuLP 建了一个混合整数规划。open_是 0-1 变量控制是否投放,cab和pb是整数变量。目标函数里收益项乘了open_,保证不投放就没有收益。约束里pb[p] * turnover >= demand[p] * open_[p]是最关键的一条,它保证投放的点位充电宝周转能力能覆盖需求。turnover=4是经验值,实际要按点位类型调,商场可以到 5 到 6,医院可能只有 2 到 3。如果你的求解器跑不出可行解,先检查这条约束是不是太紧,把 turnover 调大或 demand 调小试一下。
3.2 多目标怎么处理:利润、覆盖率、用户体验
认证杯这类题通常不会只让你优化利润,还会要求考虑覆盖率和用户体验。覆盖率就是投放点位占候选点位的比例,用户体验可以用平均等待时间或借不到的概率衡量。三个目标互相冲突:全投利润低,只投高利润点位覆盖率差,充电宝配多了成本高、配少了体验差。
常见做法是主目标加约束,或者加权求和,或者用 Pareto 前沿。我一般先用加权求和快速出一版结果,权重用熵权法或层次分析法确定,然后在论文里补一组 Pareto 分析展示权衡关系。加权求和的好处是求解快、结果稳定,缺点是权重不好解释。Pareto 前沿更漂亮但计算量大,适合点位数量少于 50 的情况。
# 加权求和示例:利润 0.6,覆盖率 0.25,体验 0.15 w = {'profit': 0.6, 'cover': 0.25, 'ux': 0.15} # 覆盖率 = 投放点位数 / 总点位数 cover = lpSum(open_[p] for p in points) / len(points) # 体验用缺货率近似:需求没被满足的比例 shortage = lpSum( (demand[p] * open_[p] - pb[p] * turnover) for p in points ) / lpSum(demand[p] for p in points) prob += w['profit'] * (收益表达式) + w['cover'] * cover - w['ux'] * shortage注意体验项是负向的,缺货率越高体验越差,所以前面加负号。权重不是拍脑袋,论文里要写清楚来源,比如用 AHP 判断矩阵算出来的,或者参考行业报告。如果题目给了明确的优先级,直接按题目来,别自己发明。
4. 避坑与排查:这道题最容易翻车的五个地方
4.1 需求预测直接拿借出量当需求
现象:模型跑出来充电宝数量偏少,实际借还总是缺货。原因:借出量是「被满足的需求」,不是真实需求。高峰期充电宝被借空后,后来的借出请求根本没记录,你拿到的数据天然偏低。解决:用归还量加库存变化反推真实需求,或者对借出量做截断修正,把满柜时段的借出量按周转率上浮 10% 到 20%。
4.2 周转率设成常数
现象:不同点位结果差异很小,商场和医院配的充电宝差不多。原因:周转率被你写死了,但商场一天能周转 5 到 6 次,医院可能只有 2 次。解决:按点位类型分档设周转率,或者用历史数据算每个点位的实际周转次数,写进参数表。
4.3 整数规划求解器报 infeasible
现象:PuLP 或 Gurobi 返回 infeasible,找不到可行解。原因:约束太紧,比如需求约束要求充电宝周转能力必须覆盖峰值需求,但机柜容量上限又卡死了。解决:先放松需求约束,改成满足 90% 需求即可,或者允许部分点位不投放。检查pb[p] <= 12 * cab[p]里的 12 是不是太小,实际机柜有 12 仓、24 仓多种规格。
4.4 忽略补货周期
现象:模型假设充电宝永远在线,实际运营中充电宝被借走后要人工补货,补货期间仓位是空的。原因:没把补货频率当约束。解决:引入补货周期变量,把可用充电宝数写成pb[p] * (1 - 补货间隔 / 24),或者直接把周转率打折。
4.5 论文里参数没有来源
现象:答辩被问「你这个 turnover=4 哪来的」,答不上来。原因:参数拍脑袋定的,没做敏感性分析。解决:对关键参数做敏感性分析,画出 turnover 从 2 到 6 时利润和覆盖率的变化曲线,论文里放一张图,说明取值依据。
5. 论文与代码打包:怎么验证你的方案能复现
5.1 用交叉验证检查需求预测的稳定性
需求预测是整条链的起点,它不稳后面全白搭。我一般会把历史数据按周切分,用前 3 周训练、第 4 周验证,算 MAPE 和 RMSE。如果 MAPE 超过 30%,说明预测模型太粗,要么加特征要么换模型。常见做法是用 Prophet 或 LightGBM 做对比,选验证集误差小的那个。注意别用未来数据训练,时间序列的交叉验证要按时间顺序切,不能随机打乱。
from sklearn.metrics import mean_absolute_percentage_error # 按时间切分,前 3 周训练,第 4 周验证 train = g[g['day'] <= 21] valid = g[g['day'] > 21] # 假设 pred 是模型输出 mape = mean_absolute_percentage_error(valid['net_smooth'], pred) print(f'MAPE: {mape:.2%}')mean_absolute_percentage_error直接给百分比误差,比 RMSE 直观。如果某些小时净流量接近 0,MAPE 会爆炸,这时候要加一个小的 epsilon 或者改用 SMAPE。
5.2 优化结果的敏感性分析怎么做
敏感性分析是论文加分项,也是验证方案鲁棒性的手段。挑 3 到 5 个关键参数,比如租借价格、机柜成本、周转率、需求增长率,每个参数取基准值的 80%、90%、100%、110%、120%,跑一遍模型,记录利润和覆盖率的变化。如果某个参数稍微一动结果就翻天覆地,说明你的方案太脆弱,要在论文里说明风险和对策。
| 参数 | 基准值 | 变化范围 | 利润变化 | 覆盖率变化 |
|---|---|---|---|---|
| 租借价格 | 3 元 | 2.4-3.6 | ±18% | ±3% |
| 机柜成本 | 2000 | 1600-2400 | ∓12% | ±5% |
| 周转率 | 4 | 3.2-4.8 | ±22% | ±8% |
| 需求增长 | 0% | -10%-+10% | ±15% | ±6% |
这张表直接放论文里,比大段文字有说服力。注意周转率对利润影响最大,说明运营效率比定价更关键,这个结论可以写进对策建议。
5.3 代码和数据的组织方式
一套能复现的方案,目录结构要清楚。我一般这么组织:data/放原始数据和清洗后的数据,src/放需求预测、点位评分、优化模型三个脚本,output/放结果表格和图,paper/放论文和图表源文件。每个脚本开头写清楚输入输出和依赖,别让人猜。随机种子固定住,np.random.seed(42)这种一定要加,否则每次跑结果不一样,答辩时说不清。
从那以后我每次做数学建模题,都强制先把数据管道跑通再碰模型,参数表单独维护一份,改一个数全流程重跑一遍。希望帮到你。
本文还有配套的精品资源,点击获取