A 题要求基于历史 6 个月货量预测未来 7 天,并进一步做集包规则与设备购置优化;B 题要求做餐厅交易数据分析、预测、备菜和套餐优化;C 题要求搜集外部数据构建“省超”热度指数、分类和潜力评估。


优惠链接:关注名片,自动回复获取链接

摘要

本文围绕 2026 年第六届长三角高校数学建模竞赛 ABC 三道赛题,给出系统化建模思路与可实现算法框架。A 题针对物流网络集包规则与设备优化问题,构建“货量预测—路由分段—混包聚合—机器/人工选择—设备投资”的混合整数规划模型;B 题针对自助量贩餐厅经营问题,建立“交易分析—需求预测—备菜优化—套餐设计”的数据驱动运营优化框架;C 题针对“谁是下一个苏超”问题,提出基于熵权法、聚类分析、余弦相似度和 TOPSIS 的省超热度与潜力评价模型。三题共同体现了数据预测、运筹优化、指标评价与实际业务解释之间的融合。


三题论文写作建议

A 题论文要突出“数学硬度”:
重点写清楚路由分段变量、混包聚合变量、格口约束、人工/机器成本、设备折旧年化模型。不要只写启发式算法,否则深度不够。

B 题论文要突出“业务闭环”:
从菜品销售规律,到营养需求预测,再到备菜和套餐,最后落到降本增效、减少浪费、提高复购率。

C 题论文要突出“指标体系可信度”:
数据来源要可靠,指标要分层,权重要有依据,聚类解释要能讲清楚“为什么苏超火”。


最推荐的得奖打法

A 题:LightGBM 预测 + MILP/CP-SAT 优化 + 启发式加速 + 敏感性分析。
敏感性分析可以讨论货量增长率、人工工资、机器格口能力变化对总成本和设备购置的影响。

B 题:EDA 可视化 + 多目标预测 + 备菜整数规划 + 套餐组合优化。
可视化越丰富越好,尤其是菜品销量长尾图、菜品共现网络、工作日需求曲线。

C 题:熵权-TOPSIS + KMeans/PCA + 苏超相似度画像。
文章表达要强,可以把苏超总结为“城市联动型群众赛事经济样板”。

如果你准备正式写论文,建议优先选 A 题或 B 题。A 题更像一等奖题,B 题更容易做完整,C 题更依赖数据搜集质量。

 

2026 长三角高校数学建模竞赛 ABC 题全套参考方案

总体选题建议

如果目标是稳妥拿奖

A 题最偏运筹优化,技术含量高,容易拉开差距。
核心是“预测 + 网络分段 + 集包规则 + 设备投资”的供应链优化问题。只要把预测模型、混包规则、格口约束、人工/机器成本和设备折旧统一进一个整数规划模型,论文会很硬。

B 题最适合数据分析队伍。
有交易流水、菜品、营养成分,容易做出漂亮可视化和业务建议。模型可用“需求预测 + 菜品组合优化 + 套餐优化”,文章表现力强。

C 题最开放,适合会爬数据、会写指标体系的队伍。
它需要自己搜集省域经济、人口、足球文化、媒体传播、赛事数据。苏超已公开有 85 场比赛、现场观众总数 243.3 万、场均 2.86 万、线上直播观看 22.2 亿等热度数据,可作为热度指数校准锚点。


A 题:物流网络集包规则及设备优化

一、问题重述与核心难点

A 题的本质是一个两阶段供应链网络优化问题

第一阶段,利用历史 6 个月首分拣—末分拣流向货量,预测未来 7 天每条 OD 流向的小件包裹量。
第二阶段,在给定走货路由下,为每个 OD 流向确定“在哪些分拣中心建包、在哪些分拣中心拆包”,并在每个场地选择机器或人工集包方式,使总成本最低。
第三阶段,考虑未来一年货量每年增长 20%,进一步决定每个分拣中心购买哪些设备、买多少台,以及对应集包规则。

难点有三个:

第一,路由是固定的,但建包路径可选
例如一条路由为:


A \rightarrow B \rightarrow C \rightarrow D

可选择只在 A 建包、D 拆包;也可选择 A-B、B-D;也可每一段都拆包重建。

第二,混包规则复杂
同一下一分拣流向的包裹不能被拆散到不同集包中;目的地相同的小件包裹,下一个拆包场地必须一致。

第三,机器格口、人工流向数、产能和成本同时约束
机器有格口数量和处理能力,人工有最大集包流向数,第三问还要加入设备购置、折旧、人工兜底。


二、A 题完整建模思路

问题 1:货量预测模型

建议采用分层集成预测模型

对每条 OD 流向 ,设历史日货量为:


y_{r,t}

预测未来 7 天:


\hat{y}_{r,t+1},\ldots,\hat{y}_{r,t+7}

推荐三类模型融合:

  1. 统计基线模型:移动平均、指数平滑、节假日/星期因子;
  2. 机器学习模型:LightGBM / XGBoost;
  3. 层级修正模型:按首分拣、末分拣、全网总量做一致性校正。

特征设计:

特征类型 变量
时间特征 dayofweek、是否周末、月份、日期序号
滞后特征 lag1、lag7、lag14、lag28
滚动特征 rolling_mean_7、rolling_mean_14、rolling_std_7
OD 特征 始发中心、目的中心、路由长度、是否跨区
层级特征 首分拣总量、末分拣总量、全网总量

预测结果要做非负修正:


\hat{y}_{r,t}=\max(0,\hat{y}_{r,t})

若题目结果表要求整数,则取:


\tilde{y}_{r,t}=\text{round}(\hat{y}_{r,t})

问题 2:集包规则优化模型

对每条 OD 流向 ,其固定路由为:


P_r=(v_{r,0},v_{r,1},\ldots,v_{r,m_r})

建包路径可以理解为对这条路由做“分段”。
若选择从节点 建包,到节点 拆包,则定义决策变量:


x_{r,p,q}=
\begin{cases}
1, & \text{流向 } r \text{ 在 } v_{r,p} \text{ 建包并在 } v_{r,q} \text{ 拆包}\\
0, & \text{否则}
\end{cases}

其中:


0 \le p < q \le m_r

为了保证每条 OD 流向形成一条完整、连续、唯一的建包路径,可以把每条路由看作一个 DAG,从第 0 个节点走到第 个节点:


\sum_{q>0} x_{r,0,q}=1

\sum_{p<k}x_{r,p,k}=\sum_{q>k}x_{r,k,q},\quad k=1,\ldots,m_r-1

\sum_{p<m_r}x_{r,p,m_r}=1

这样天然保证一条 OD 只会选择唯一的建包拆包路径。


混包与流向聚合

在分拣中心 ,若多个 OD 流向在 建包,并且它们下一跳相同、拆包点相同,则可以合并为一个集包流向组:


g=(i,j,k)

其中:

  • :当前建包中心;
  • :下一分拣中心;
  • :下一拆包中心。

该组货量为:


Q_g=\sum_{r,p,q} \hat{y}_r x_{r,p,q}

需要满足:


v_{r,p}=i,\quad v_{r,p+1}=j,\quad v_{r,q}=k

这一步非常关键。
普通做法会把每个 OD 单独算成本,但优秀论文应该把相同集包规则下的流向聚合,这才符合“混包”的实际含义。


机器与人工选择

定义:


z_g^M=
\begin{cases}
1, & \text{集包组 } g \text{ 使用机器}\\
0, & \text{否则}
\end{cases}

z_g^H=
\begin{cases}
1, & \text{集包组 } g \text{ 使用人工}\\
0, & \text{否则}
\end{cases}

约束:


z_g^M+z_g^H=1

若机器每个格口最多处理 个包裹,则集包组 需要格口数:


b_g=\left\lceil \frac{Q_g}{a_i}\right\rceil

机器格口约束:


\sum_{g:i(g)=i} b_g z_g^M \le B_i

机器产能约束:


\sum_{g:i(g)=i} Q_g z_g^M \le C_i^M

人工最大流向数约束:


\sum_{g:i(g)=i} z_g^H \le L_i

人工产能约束:


\sum_{g:i(g)=i} Q_g z_g^H \le C_i^H

目标函数:


\min \sum_g \left(c_{i(g)}^M Q_g z_g^M+c_{i(g)}^H Q_g z_g^H\right)

如果题中成本是按“流向”“格口”或“包裹量”计价,应根据附件表 3 的字段调整。


问题 3:设备购置优化

第三问在第二问基础上增加设备决策。
假设设备类型集合为 ,每台设备有:

  • 格口数
  • 产能
  • 购置成本
  • 折旧年限

设场地 购买设备 的数量为:


n_{i,e}\in \mathbb{Z}_{\ge0}

未来一年货量增长 20%,若当前预测日货量为 ,则年化日均货量可取:


\hat y_r^{year}=1.2\hat y_r

或者若考虑逐月增长,可以用:


\hat y_{r,m}=\hat y_r(1+20\%)^{m/12}

设备折旧日成本:


d_e=\frac{K_e}{365Y_e}

设备格口约束变为:


\sum_g b_g z_g^M \le \sum_e B_e n_{i,e}

设备产能约束变为:


\sum_g Q_g z_g^M \le \sum_e C_e n_{i,e}

目标函数:


\min 
\sum_{i,e}365d_e n_{i,e}
+
\sum_g 365c_{i(g)}^M Q_g z_g^M
+
\sum_g 365c_{i(g)}^H Q_g z_g^H
+
\text{人工兜底成本}

题目说明人工每人每天最多处理 5 个格口,每人每天工资 90 元。若机器不足,人工兜底人数为:


w_i=\left\lceil \frac{\sum_{g:i(g)=i} b_gz_g^H}{5}\right\rceil

人工工资成本:


90\times365\sum_i w_i

三、A 题 Python 代码框架

# A题:物流网络预测 + 集包规则 + 设备优化
# 依赖:pandas, numpy, lightgbm, sklearn, ortools
# pip install pandas numpy scikit-learn lightgbm ortools openpyxl

import pandas as pd
import numpy as np
from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import TimeSeriesSplit
from lightgbm import LGBMRegressor
from ortools.sat.python import cp_model
import math

# ======================
# 1. 读取数据
# ======================

route_df = pd.read_excel("附件表1_走货路由.xlsx")
hist_df = pd.read_excel("附件表2_历史货量.xlsx")
site_df = pd.read_excel("附件表3_场地成本产能.xlsx")
device_df = pd.read_excel("附件表4_设备信息.xlsx")

# 假设 hist_df 字段:
# origin, dest, date, volume
hist_df["date"] = pd.to_datetime(hist_df["date"])
hist_df = hist_df.sort_values(["origin", "dest", "date"])

# ======================
# 2. 特征工程
# ======================

def make_features(df):
    df = df.copy()
    df["dow"] = df["date"].dt.dayofweek
    df["month"] = df["date"].dt.month
    df["day"] = df["date"].dt.day
    df["is_weekend"] = (df["dow"] >= 5).astype(int)
    
    df["od"] = df["origin"].astype(str) + "_" + df["dest"].astype(str)
    
    for lag in [1, 2, 3, 7, 14, 28]:
        df[f"lag_{lag}"] = df.groupby("od")["volume"].shift(lag)
    
    for win in [7, 14, 28]:
        df[f"roll_mean_{win}"] = (
            df.groupby("od")["volume"]
            .shift(1)
            .rolling(win)
            .mean()
            .reset_index(level=0, drop=True)
        )
        df[f"roll_std_{win}"] = (
            df.groupby("od")["volume"]
            .shift(1)
            .rolling(win)
            .std()
            .reset_index(level=0, drop=True)
        )
    
    return df

feat_df = make_features(hist_df)
feat_df = feat_df.dropna()

features = [
    "dow", "month", "day", "is_weekend",
    "lag_1", "lag_2", "lag_3", "lag_7", "lag_14", "lag_28",
    "roll_mean_7", "roll_std_7",
    "roll_mean_14", "roll_std_14",
    "roll_mean_28", "roll_std_28"
]

# 类别变量编码
feat_df["origin_code"] = feat_df["origin"].astype("category").cat.codes
feat_df["dest_code"] = feat_df["dest"].astype("category").cat.codes
features += ["origin_code", "dest_code"]

# ======================
# 3. 训练预测模型
# ======================

train = feat_df.copy()
X = train[features]
y = train["volume"]

model = LGBMRegressor(
    n_estimators=800,
    learning_rate=0.03,
    max_depth=-1,
    num_leaves=64,
    subsample=0.85,
    colsample_bytree=0.85,
    random_state=2026
)

model.fit(X, y)

# ======================
# 4. 递推预测未来7天
# ======================

def forecast_7_days(hist_df, model, features):
    all_pred = []
    work_df = hist_df.copy()
    last_date = work_df["date"].max()
    
    for h in range(1, 8):
        pred_date = last_date + pd.Timedelta(days=h)
        ods = work_df[["origin", "dest"]].drop_duplicates()
        future = ods.copy()
        future["date"] = pred_date
        future["volume"] = np.nan
        
        tmp = pd.concat([work_df, future], ignore_index=True)
        tmp = tmp.sort_values(["origin", "dest", "date"])
        tmp = make_features(tmp)
        
        pred_rows = tmp[tmp["date"] == pred_date].copy()
        pred_rows["origin_code"] = pred_rows["origin"].astype("category").cat.codes
        pred_rows["dest_code"] = pred_rows["dest"].astype("category").cat.codes
        
        pred_rows[features] = pred_rows[features].fillna(0)
        pred = model.predict(pred_rows[features])
        pred = np.maximum(pred, 0)
        
        pred_rows["pred_volume"] = np.round(pred).astype(int)
        all_pred.append(pred_rows[["origin", "dest", "date", "pred_volume"]])
        
        append_df = pred_rows[["origin", "dest", "date"]].copy()
        append_df["volume"] = pred_rows["pred_volume"].values
        work_df = pd.concat([work_df, append_df], ignore_index=True)
    
    return pd.concat(all_pred, ignore_index=True)

pred_df = forecast_7_days(hist_df, model, features)
pred_df.to_excel("结果表1_未来7天货量预测.xlsx", index=False)

# ======================
# 5. 构建路由字典
# ======================

# 假设 route_df 字段:origin, dest, route
# route 形如 "A-B-C-D"
def parse_route(s):
    return str(s).split("-")

route_dict = {}
for _, row in route_df.iterrows():
    key = (row["origin"], row["dest"])
    route_dict[key] = parse_route(row["route"])

# 取未来7天总量或日均量作为优化输入
flow_df = pred_df.groupby(["origin", "dest"])["pred_volume"].sum().reset_index()
flow = {
    (row["origin"], row["dest"]): int(row["pred_volume"])
    for _, row in flow_df.iterrows()
}

# ======================
# 6. 集包规则优化:CP-SAT示意
# ======================

def optimize_bagging(route_dict, flow, site_df, scale=100):
    """
    说明:
    CP-SAT只能处理整数,因此成本可乘scale转整数。
    该框架展示核心结构,实际需要按附件字段改列名。
    """
    model = cp_model.CpModel()
    
    # site参数
    site_param = {}
    for _, row in site_df.iterrows():
        site = row["site"]
        site_param[site] = {
            "machine_cost": int(row["machine_cost"] * scale),
            "human_cost": int(row["human_cost"] * scale),
            "machine_capacity": int(row["machine_capacity"]),
            "human_capacity": int(row["human_capacity"]),
            "port_num": int(row["port_num"]),
            "a": int(row["a"])
        }
    
    # x变量:OD在p建包,q拆包
    x = {}
    for od, path in route_dict.items():
        m = len(path) - 1
        for p in range(m):
            for q in range(p + 1, m + 1):
                x[(od, p, q)] = model.NewBoolVar(f"x_{od}_{p}_{q}")
    
    # 每条OD形成连续路径
    for od, path in route_dict.items():
        m = len(path) - 1
        # 起点流出
        model.Add(sum(x[(od, 0, q)] for q in range(1, m + 1)) == 1)
        # 中间节点流守恒
        for k in range(1, m):
            inflow = sum(x[(od, p, k)] for p in range(0, k))
            outflow = sum(x[(od, k, q)] for q in range(k + 1, m + 1))
            model.Add(inflow == outflow)
        # 终点流入
        model.Add(sum(x[(od, p, m)] for p in range(0, m)) == 1)
    
    # 聚合集包组 g=(i,next_node,unpack_node)
    groups = {}
    for od, path in route_dict.items():
        q_od = int(flow.get(od, 0))
        if q_od == 0:
            continue
        m = len(path) - 1
        for p in range(m):
            for q in range(p + 1, m + 1):
                i = path[p]
                nxt = path[p + 1]
                unpack = path[q]
                g = (i, nxt, unpack)
                groups.setdefault(g, [])
                groups[g].append((od, p, q, q_od))
    
    # 组是否启用、机器/人工
    use_g, machine_g, human_g, volume_g, port_g = {}, {}, {}, {}, {}
    max_volume = max(sum(flow.values()), 1)
    
    for g, items in groups.items():
        i = g[0]
        use_g[g] = model.NewBoolVar(f"use_{g}")
        machine_g[g] = model.NewBoolVar(f"machine_{g}")
        human_g[g] = model.NewBoolVar(f"human_{g}")
        volume_g[g] = model.NewIntVar(0, max_volume, f"vol_{g}")
        
        # volume_g = sum flow_od * x
        model.Add(
            volume_g[g] == sum(q_od * x[(od, p, q)] for od, p, q, q_od in items)
        )
        
        # 如果volume>0则use=1;这里用弱连接,实际可加更严格big-M
        model.Add(volume_g[g] <= max_volume * use_g[g])
        model.Add(machine_g[g] + human_g[g] == use_g[g])
        
        a_i = max(site_param[i]["a"], 1)
        max_port = math.ceil(max_volume / a_i)
        port_g[g] = model.NewIntVar(0, max_port, f"port_{g}")
        # port >= volume/a
        model.Add(port_g[g] * a_i >= volume_g[g])
        model.Add(port_g[g] <= max_port * use_g[g])
    
    # 场地资源约束
    for site, param in site_param.items():
        gs = [g for g in groups if g[0] == site]
        if not gs:
            continue
        
        # 机器格口
        model.Add(
            sum(port_g[g] * machine_g[g] for g in gs) <= param["port_num"]
        )
        # 机器产能
        model.Add(
            sum(volume_g[g] * machine_g[g] for g in gs) <= param["machine_capacity"]
        )
        # 人工产能
        model.Add(
            sum(volume_g[g] * human_g[g] for g in gs) <= param["human_capacity"]
        )
    
    # 目标函数
    obj_terms = []
    for g in groups:
        i = g[0]
        cm = site_param[i]["machine_cost"]
        ch = site_param[i]["human_cost"]
        obj_terms.append(cm * volume_g[g] * machine_g[g])
        obj_terms.append(ch * volume_g[g] * human_g[g])
    
    model.Minimize(sum(obj_terms))
    
    solver = cp_model.CpSolver()
    solver.parameters.max_time_in_seconds = 600
    solver.parameters.num_search_workers = 8
    status = solver.Solve(model)
    
    result = []
    if status in [cp_model.OPTIMAL, cp_model.FEASIBLE]:
        for g in groups:
            if solver.Value(use_g[g]) == 1:
                result.append({
                    "build_site": g[0],
                    "next_site": g[1],
                    "unpack_site": g[2],
                    "volume": solver.Value(volume_g[g]),
                    "ports": solver.Value(port_g[g]),
                    "method": "machine" if solver.Value(machine_g[g]) == 1 else "human"
                })
    return pd.DataFrame(result)

bag_result = optimize_bagging(route_dict, flow, site_df)
bag_result.to_excel("结果表2_集包规则.xlsx", index=False)

# ======================
# 7. 设备购置优化框架
# ======================

def optimize_devices(bag_result, site_df, device_df, growth=1.2):
    """
    简化版:先根据第二问集包结果估算各场地需求,
    再用整数规划选择设备组合。
    """
    demand = bag_result.copy()
    demand["future_volume"] = demand["volume"] * growth
    site_need = demand.groupby("build_site").agg({
        "future_volume": "sum",
        "ports": "sum"
    }).reset_index()
    
    model = cp_model.CpModel()
    n = {}
    
    max_devices = 50
    for _, srow in site_need.iterrows():
        site = srow["build_site"]
        for _, drow in device_df.iterrows():
            dev = drow["device_type"]
            n[(site, dev)] = model.NewIntVar(0, max_devices, f"n_{site}_{dev}")
    
    total_cost = []
    for _, srow in site_need.iterrows():
        site = srow["build_site"]
        need_port = int(math.ceil(srow["ports"]))
        need_cap = int(math.ceil(srow["future_volume"]))
        
        port_sum = []
        cap_sum = []
        for _, drow in device_df.iterrows():
            dev = drow["device_type"]
            port_sum.append(int(drow["port_num"]) * n[(site, dev)])
            cap_sum.append(int(drow["capacity"]) * n[(site, dev)])
            annual_cost = int(drow["cost"] / max(drow["depreciation_years"], 1))
            total_cost.append(annual_cost * n[(site, dev)])
        
        model.Add(sum(port_sum) >= need_port)
        model.Add(sum(cap_sum) >= need_cap)
    
    model.Minimize(sum(total_cost))
    solver = cp_model.CpSolver()
    solver.parameters.max_time_in_seconds = 300
    solver.parameters.num_search_workers = 8
    status = solver.Solve(model)
    
    rows = []
    if status in [cp_model.OPTIMAL, cp_model.FEASIBLE]:
        for key, var in n.items():
            val = solver.Value(var)
            if val > 0:
                rows.append({
                    "site": key[0],
                    "device_type": key[1],
                    "quantity": val
                })
    return pd.DataFrame(rows)

device_result = optimize_devices(bag_result, site_df, device_df)
device_result.to_excel("结果表3_设备购置方案.xlsx", index=False)

B 题:自助量贩餐厅菜量需求预测与运营优化设计

一、问题重述

B 题是典型的餐饮经营数据建模问题
题目要求基于餐厅流水数据、每次消费菜品及营养成分数据,完成:

  1. 历史交易数据预处理、统计和可视化;
  2. 预测 2025 年 5 月工作日的就餐人数、营养素需求量、销售总额;
  3. 给出 2025 年 5 月 6 日至 5 月 12 日工作日午餐、晚餐备菜方案;
  4. 设计 10 元、15 元、20 元套餐;
  5. 给出经营优化建议。

二、B 题建模路线

问题 1:数据预处理与关联分析

预处理步骤:

  1. 删除重复订单;
  2. 处理缺失菜品、异常价格、异常营养值;
  3. 将交易流水表和菜品营养表按订单号合并;
  4. 构造订单级、菜品级、日级、餐段级数据。

核心统计:

分析对象 指标
菜品销量 均值、中位数、标准差、长尾分布
销售额 日销售额、午晚餐销售额
就餐人数 工作日/周末、午餐/晚餐
营养素 蛋白质、脂肪、碳水、热量等
关联关系 菜品共现、Apriori 关联规则、菜品相关系数

菜品销量通常满足长尾分布:少数高频菜贡献大部分销量。
可用帕累托图证明:


\frac{\sum_{i=1}^{k} sales_i}{\sum_{i=1}^{n} sales_i}

问题 2:需求预测模型

预测对象:


N_t:每日就餐人数

R_t:每日销售总额

M_{k,t}:第k类营养素需求量

推荐模型:

  1. 日级总量:LightGBM / XGBoost;
  2. 稳定营养素:SARIMA / 指数平滑;
  3. 多目标统一预测:多输出随机森林或 LightGBM 分目标训练。

特征:

特征 说明
日期 星期、月份、是否工作日
滞后 lag1、lag7、lag14
滚动 7日均值、14日均值
餐段 午餐、晚餐
价格结构 客单价、菜品均价
菜品结构 热销菜占比、荤素比例

预测可靠性讨论:

  • 使用时间序列交叉验证;
  • 用 MAE、RMSE、MAPE 评价;
  • 画真实值与预测值对比;
  • 对节假日、极端天气、促销活动说明模型局限性。

问题 3:备菜优化模型

设:


q_{d,m,t}

表示第 天、第 餐段,菜品 的备菜份数。

预测需求为:


\hat{s}_{d,m,t}

目标是最大化利润,或最小化缺货与浪费综合损失:


\min 
\sum_{d,m,t}
\left[
c_d^w(q_{d,m,t}-\hat{s}_{d,m,t})_+
+
c_d^o(\hat{s}_{d,m,t}-q_{d,m,t})_+
\right]

其中:

  • :剩菜浪费成本;
  • :缺货机会损失。

加入安全库存:


q_{d,m,t}\ge \hat{s}_{d,m,t}+z_{\alpha}\sigma_{d,m}

营养约束:


L_k \le \sum_d q_{d,m,t} a_{d,k} \le U_k

菜品多样性约束:


\sum_d I(q_{d,m,t}>0) \ge D_{\min}

荤素比例约束:


\rho_{\min}
\le
\frac{\sum_{d\in Meat}q_{d,m,t}}{\sum_d q_{d,m,t}}
\le
\rho_{\max}

消费习惯约束:

高频菜品必须保留:


q_{d,m,t}\ge Q_d^{min},\quad d\in Hot

问题 4:套餐设计模型

套餐价位为:


P\in\{10,15,20\}

设套餐是否包含菜品 :


x_d\in\{0,1\}

套餐成本:


\sum_d cost_d x_d \le \eta P

其中 是成本率上限,例如 0.55—0.70。

营养均衡目标:


\min \sum_k w_k
\left(
\frac{\sum_d a_{d,k}x_d-T_k}{T_k}
\right)^2

消费偏好收益:


\max \sum_d pref_d x_d

综合目标:


\max 
\alpha \sum_d pref_dx_d
-
\beta \sum_k w_k
\left(
\frac{\sum_d a_{d,k}x_d-T_k}{T_k}
\right)^2
-
\gamma \left|\sum_d price_dx_d-P\right|

约束:


\sum_d price_dx_d \le P

n_{\min}\le \sum_d x_d\le n_{\max}

\sum_{d\in staple}x_d\ge1

\sum_{d\in protein}x_d\ge1

\sum_{d\in vegetable}x_d\ge1

三、B 题 Python 代码框架

# B题:餐厅需求预测 + 备菜优化 + 套餐设计
# pip install pandas numpy scikit-learn lightgbm mlxtend pulp openpyxl matplotlib

import pandas as pd
import numpy as np
from lightgbm import LGBMRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error
from mlxtend.frequent_patterns import apriori, association_rules
import pulp
import matplotlib.pyplot as plt

# ======================
# 1. 数据读取
# ======================

orders = pd.read_excel("附件1_餐厅流水.xlsx")
items = pd.read_excel("附件2_菜品营养.xlsx")

# 假设字段:
# orders: order_id, time, amount, customer_id
# items: order_id, dish, price, cost, calories, protein, fat, carb, category

orders["time"] = pd.to_datetime(orders["time"])
orders["date"] = orders["time"].dt.date
orders["hour"] = orders["time"].dt.hour
orders["meal"] = np.where(orders["hour"] < 15, "lunch", "dinner")

data = items.merge(
    orders[["order_id", "time", "date", "meal", "amount", "customer_id"]],
    on="order_id",
    how="left"
)

# ======================
# 2. 统计分析
# ======================

dish_sales = data.groupby("dish").agg(
    sales_qty=("dish", "count"),
    sales_amount=("price", "sum"),
    avg_price=("price", "mean")
).reset_index().sort_values("sales_qty", ascending=False)

dish_sales.to_excel("B_菜品销量统计.xlsx", index=False)

# 菜品共现矩阵
basket = pd.crosstab(data["order_id"], data["dish"])
basket = basket.applymap(lambda x: 1 if x > 0 else 0)

freq = apriori(basket, min_support=0.02, use_colnames=True)
rules = association_rules(freq, metric="lift", min_threshold=1.1)
rules.to_excel("B_菜品关联规则.xlsx", index=False)

# ======================
# 3. 构造日级预测数据
# ======================

daily = orders.groupby("date").agg(
    people=("customer_id", "nunique"),
    sales=("amount", "sum"),
    orders=("order_id", "nunique")
).reset_index()

nut_daily = data.groupby("date").agg(
    calories=("calories", "sum"),
    protein=("protein", "sum"),
    fat=("fat", "sum"),
    carb=("carb", "sum")
).reset_index()

daily = daily.merge(nut_daily, on="date", how="left")
daily["date"] = pd.to_datetime(daily["date"])
daily = daily.sort_values("date")

def make_ts_features(df, target):
    out = df.copy()
    out["dow"] = out["date"].dt.dayofweek
    out["month"] = out["date"].dt.month
    out["day"] = out["date"].dt.day
    out["is_workday"] = (out["dow"] < 5).astype(int)
    for lag in [1, 2, 3, 7, 14]:
        out[f"{target}_lag_{lag}"] = out[target].shift(lag)
    for win in [7, 14]:
        out[f"{target}_roll_mean_{win}"] = out[target].shift(1).rolling(win).mean()
        out[f"{target}_roll_std_{win}"] = out[target].shift(1).rolling(win).std()
    return out

def train_forecast(df, target, future_dates):
    feat_df = make_ts_features(df, target).dropna()
    feature_cols = [c for c in feat_df.columns if c not in ["date", target]]
    
    model = LGBMRegressor(
        n_estimators=500,
        learning_rate=0.03,
        num_leaves=31,
        random_state=2026
    )
    model.fit(feat_df[feature_cols], feat_df[target])
    
    work = df.copy()
    preds = []
    
    for d in future_dates:
        new = pd.DataFrame({"date": [pd.to_datetime(d)], target: [np.nan]})
        tmp = pd.concat([work[["date", target]], new], ignore_index=True)
        tmp = make_ts_features(tmp, target)
        row = tmp[tmp["date"] == pd.to_datetime(d)].copy()
        row = row.fillna(0)
        pred = max(0, model.predict(row[feature_cols])[0])
        preds.append({"date": d, target: pred})
        
        append = pd.DataFrame({"date": [pd.to_datetime(d)], target: [pred]})
        work = pd.concat([work[["date", target]], append], ignore_index=True)
    
    return pd.DataFrame(preds)

future_dates = pd.bdate_range("2025-05-01", "2025-05-31")
targets = ["people", "sales", "calories", "protein", "fat", "carb"]

pred_list = []
for target in targets:
    pred = train_forecast(daily[["date", target]].dropna(), target, future_dates)
    pred_list.append(pred.rename(columns={target: f"pred_{target}"}))

pred_result = pred_list[0]
for p in pred_list[1:]:
    pred_result = pred_result.merge(p, on="date")

pred_result.to_excel("B_2025年5月工作日预测结果.xlsx", index=False)

# ======================
# 4. 备菜优化
# ======================

# 菜品日餐段需求预测:简单用历史均值比例分配
dish_meal = data.groupby(["date", "meal", "dish"]).size().reset_index(name="qty")
dish_profile = dish_meal.groupby(["meal", "dish"])["qty"].mean().reset_index()

dish_info = data.groupby("dish").agg(
    price=("price", "mean"),
    cost=("cost", "mean"),
    calories=("calories", "mean"),
    protein=("protein", "mean"),
    fat=("fat", "mean"),
    carb=("carb", "mean"),
    category=("category", "first")
).reset_index()

def optimize_menu_for_day(meal, total_people, dish_profile, dish_info):
    dishes = dish_info["dish"].tolist()
    prob = pulp.LpProblem("menu_optimization", pulp.LpMinimize)
    
    q = {
        d: pulp.LpVariable(f"q_{d}", lowBound=0, cat="Integer")
        for d in dishes
    }
    shortage = {
        d: pulp.LpVariable(f"short_{d}", lowBound=0)
        for d in dishes
    }
    waste = {
        d: pulp.LpVariable(f"waste_{d}", lowBound=0)
        for d in dishes
    }
    
    # 需求估计
    prof = dish_profile[dish_profile["meal"] == meal].copy()
    total_avg = prof["qty"].sum()
    demand = {}
    for _, row in prof.iterrows():
        demand[row["dish"]] = row["qty"] / total_avg * total_people if total_avg > 0 else 0
    
    for d in dishes:
        dem = demand.get(d, 0)
        prob += q[d] - dem == waste[d] - shortage[d]
    
    # 目标:浪费 + 缺货
    prob += pulp.lpSum([1.0 * waste[d] + 2.0 * shortage[d] for d in dishes])
    
    # 多样性:至少20个菜有供应
    y = {
        d: pulp.LpVariable(f"y_{d}", lowBound=0, upBound=1, cat="Binary")
        for d in dishes
    }
    M = 10000
    for d in dishes:
        prob += q[d] <= M * y[d]
    prob += pulp.lpSum([y[d] for d in dishes]) >= min(20, len(dishes))
    
    # 总份数接近人数 * 人均菜品数
    prob += pulp.lpSum([q[d] for d in dishes]) >= total_people * 2
    prob += pulp.lpSum([q[d] for d in dishes]) <= total_people * 5
    
    prob.solve(pulp.PULP_CBC_CMD(msg=False))
    
    rows = []
    for d in dishes:
        val = q[d].value()
        if val and val > 0:
            rows.append({"meal": meal, "dish": d, "quantity": round(val)})
    return pd.DataFrame(rows)

plans = []
for d in pd.bdate_range("2025-05-06", "2025-05-12"):
    people_pred = float(pred_result[pred_result["date"] == d]["pred_people"].iloc[0])
    for meal in ["lunch", "dinner"]:
        meal_people = people_pred * (0.55 if meal == "lunch" else 0.45)
        plan = optimize_menu_for_day(meal, meal_people, dish_profile, dish_info)
        plan["date"] = d
        plans.append(plan)

menu_plan = pd.concat(plans, ignore_index=True)
menu_plan.to_excel("B_5月6日至12日备菜方案.xlsx", index=False)

# ======================
# 5. 套餐优化
# ======================

def optimize_package(price_limit, dish_info):
    dishes = dish_info["dish"].tolist()
    prob = pulp.LpProblem(f"package_{price_limit}", pulp.LpMaximize)
    
    x = {
        d: pulp.LpVariable(f"x_{d}", lowBound=0, upBound=1, cat="Binary")
        for d in dishes
    }
    
    price = dict(zip(dish_info["dish"], dish_info["price"]))
    cost = dict(zip(dish_info["dish"], dish_info["cost"]))
    protein = dict(zip(dish_info["dish"], dish_info["protein"]))
    calories = dict(zip(dish_info["dish"], dish_info["calories"]))
    
    # 偏好用历史销量归一化
    sales_pref = data.groupby("dish").size()
    pref = {d: float(sales_pref.get(d, 0)) for d in dishes}
    
    prob += pulp.lpSum([pref[d] * x[d] for d in dishes])
    
    prob += pulp.lpSum([price[d] * x[d] for d in dishes]) <= price_limit
    prob += pulp.lpSum([cost[d] * x[d] for d in dishes]) <= 0.65 * price_limit
    prob += pulp.lpSum([x[d] for d in dishes]) >= 3
    prob += pulp.lpSum([x[d] for d in dishes]) <= 6
    
    # 营养下限,具体值需结合数据尺度调整
    prob += pulp.lpSum([protein[d] * x[d] for d in dishes]) >= 10
    prob += pulp.lpSum([calories[d] * x[d] for d in dishes]) >= 300
    
    prob.solve(pulp.PULP_CBC_CMD(msg=False))
    
    rows = []
    for d in dishes:
        if x[d].value() == 1:
            rows.append({
                "package_price": price_limit,
                "dish": d,
                "price": price[d],
                "cost": cost[d],
                "protein": protein[d],
                "calories": calories[d]
            })
    return pd.DataFrame(rows)

packages = pd.concat([
    optimize_package(10, dish_info),
    optimize_package(15, dish_info),
    optimize_package(20, dish_info)
], ignore_index=True)

packages.to_excel("B_套餐设计方案.xlsx", index=False)

C 题:谁是下一个“苏超”?

一、问题重述

C 题要求研究“省超”热度差异、相似性分类和潜力省份选择。
给定 15 个对象,包括苏超、齐鲁超赛、粤超、浙超、楚超、闽超、赣超、湘超、蒙超、疆超、渝超、川超、滇超、琼超、东北超等。需要构建多维指标体系,分析核心驱动力,找出与苏超最相似的 2—3 个省超,并在其他省份中找出最具举办潜力的 3 个。


二、C 题数据指标体系

建议构建五大一级指标:

一级指标 二级指标
赛事热度 现场观众、场均观众、直播观看量、短视频播放量、搜索指数
经济基础 GDP、人均 GDP、居民消费支出、第三产业占比、文旅消费
人口与城市结构 常住人口、城镇化率、城市数量、城市间交通时间
足球文化 注册球员、足球场地数量、校园足球学校、职业俱乐部数量
传播能力 社交媒体指数、媒体报道量、短视频互动、地方文旅联动

苏超的成功,不只来自足球本身,而是“城市竞争叙事 + 低门槛群众体育 + 地方文旅营销 + 短视频传播 + 高密度城市网络”的叠加。公开报道显示,2025 苏超现场观众总数 243.3 万、线上直播观看 22.2 亿,是构建热度指数的重要标杆。


三、问题 1:省超热度指数模型

定义省超热度指数:


H_i=
w_1A_i+w_2B_i+w_3C_i+w_4D_i+w_5E_i

其中:

  • :现场观赛指数;
  • :线上传播指数;
  • :城市参与指数;
  • :商业转化指数;
  • :社会话题指数。

对正向指标归一化:


x'_{ij}=\frac{x_{ij}-\min x_j}{\max x_j-\min x_j}

对负向指标归一化:


x'_{ij}=\frac{\max x_j-x_{ij}}{\max x_j-\min x_j}

权重建议用熵权法 + AHP 修正

熵权法:


p_{ij}=\frac{x'_{ij}}{\sum_i x'_{ij}}

e_j=-\frac{1}{\ln n}\sum_i p_{ij}\ln p_{ij}

w_j=\frac{1-e_j}{\sum_j(1-e_j)}

四、问题 2:15 个省超相似性、聚类与苏超对标

对每个省超构建特征向量:


X_i=(x_{i1},x_{i2},\ldots,x_{ip})

先标准化,再使用:

  1. K-means 聚类;
  2. 层次聚类;
  3. PCA 降维可视化;
  4. 余弦相似度寻找与苏超最相似对象。

余弦相似度:


sim(i,JS)=
\frac{X_i\cdot X_{JS}}{\|X_i\|\|X_{JS}\|}

建议分类结果写成四类:

类型 特征 代表
强经济强传播型 GDP 高、媒体强、城市密集 苏超、粤超、浙超
人口大省潜力型 人口多、消费强、联赛基础待提升 鲁、川、鄂、湘
文旅特色驱动型 地域文化强、旅游场景好 闽、赣、琼、滇
边疆特色参与型 地域跨度大、民族文化强 蒙、疆、东北超

与苏超最相似的候选一般会集中在:

浙江、广东、山东
原因是它们经济体量、城市网络、媒体传播、消费能力和区域竞争叙事都较强。但最终要以你们采集数据后的余弦相似度/TOPSIS 排名为准。


五、问题 3:其他省份举办潜力评估

排除 15 个已有省超对象,对其他省份建立潜力指数:


P_i=
\alpha E_i+\beta Pop_i+\gamma T_i+\delta F_i+\theta M_i

其中:

  • :经济消费潜力;
  • :人口和城市规模;
  • :交通与城市网络;
  • :足球基础;
  • :传播和文旅营销能力。

可用 TOPSIS 排名。

理想最优解:


A^+=\{\max z_{ij}\}

理想最劣解:


A^-=\{\min z_{ij}\}

距离:


D_i^+=\sqrt{\sum_j w_j(z_{ij}-A_j^+)^2}

D_i^-=\sqrt{\sum_j w_j(z_{ij}-A_j^-)^2}

贴近度:


C_i=\frac{D_i^-}{D_i^++D_i^-}

越高,举办潜力越大。


六、C 题 Python 代码框架

# C题:省超热度指数 + 聚类 + TOPSIS
# pip install pandas numpy scikit-learn matplotlib openpyxl

import pandas as pd
import numpy as np
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.metrics.pairwise import cosine_similarity
import matplotlib.pyplot as plt

# ======================
# 1. 读取指标数据
# ======================

# 需要自己整理 province_super_data.xlsx
# 字段示例:
# name, province, audience_total, audience_avg, online_views,
# gdp, population, urbanization, football_fields,
# clubs, media_index, tourism_income, transport_density

df = pd.read_excel("C_省超指标数据.xlsx")

id_cols = ["name", "province"]
indicator_cols = [c for c in df.columns if c not in id_cols]

# ======================
# 2. 缺失值处理
# ======================

for c in indicator_cols:
    df[c] = df[c].fillna(df[c].median())

# ======================
# 3. 归一化
# ======================

scaler = MinMaxScaler()
X_norm = scaler.fit_transform(df[indicator_cols])
X_norm = pd.DataFrame(X_norm, columns=indicator_cols)

# ======================
# 4. 熵权法
# ======================

def entropy_weight(X):
    X = np.array(X, dtype=float)
    X = X + 1e-12
    P = X / X.sum(axis=0)
    n = X.shape[0]
    E = -np.sum(P * np.log(P), axis=0) / np.log(n)
    D = 1 - E
    W = D / D.sum()
    return W

weights = entropy_weight(X_norm)

weight_df = pd.DataFrame({
    "indicator": indicator_cols,
    "weight": weights
}).sort_values("weight", ascending=False)

weight_df.to_excel("C_指标权重.xlsx", index=False)

# ======================
# 5. 热度指数
# ======================

df["heat_index"] = np.dot(X_norm, weights)
df_rank = df.sort_values("heat_index", ascending=False)
df_rank.to_excel("C_省超热度指数排名.xlsx", index=False)

# ======================
# 6. 聚类分析
# ======================

X_std = StandardScaler().fit_transform(df[indicator_cols])

# 可用肘部法选择K
sse = []
for k in range(2, 8):
    km = KMeans(n_clusters=k, random_state=2026, n_init=20)
    km.fit(X_std)
    sse.append(km.inertia_)

# 假设选择4类
kmeans = KMeans(n_clusters=4, random_state=2026, n_init=20)
df["cluster"] = kmeans.fit_predict(X_std)

cluster_summary = df.groupby("cluster")[indicator_cols + ["heat_index"]].mean()
cluster_summary.to_excel("C_聚类特征描述.xlsx")

# PCA可视化
pca = PCA(n_components=2)
coord = pca.fit_transform(X_std)
df["pc1"] = coord[:, 0]
df["pc2"] = coord[:, 1]

plt.figure(figsize=(8, 6))
for c in sorted(df["cluster"].unique()):
    sub = df[df["cluster"] == c]
    plt.scatter(sub["pc1"], sub["pc2"], label=f"Cluster {c}")
for _, row in df.iterrows():
    plt.text(row["pc1"], row["pc2"], row["name"], fontsize=8)
plt.legend()
plt.title("省超聚类PCA可视化")
plt.savefig("C_省超聚类PCA.png", dpi=300, bbox_inches="tight")

# ======================
# 7. 与苏超相似度
# ======================

names = df["name"].tolist()
js_idx = df.index[df["name"].str.contains("苏超")][0]

sim = cosine_similarity(X_std, X_std[js_idx].reshape(1, -1)).ravel()
df["similarity_to_suchao"] = sim

similar_rank = df[df.index != js_idx].sort_values(
    "similarity_to_suchao", ascending=False
)

similar_rank[["name", "province", "similarity_to_suchao"]].head(5).to_excel(
    "C_与苏超最相似省超.xlsx", index=False
)

# ======================
# 8. TOPSIS潜力评估
# ======================

# 对15个以外省份建立数据表
pot = pd.read_excel("C_其他省份潜力指标.xlsx")
pot_cols = [c for c in pot.columns if c not in ["province"]]

for c in pot_cols:
    pot[c] = pot[c].fillna(pot[c].median())

Z = MinMaxScaler().fit_transform(pot[pot_cols])
W = entropy_weight(Z)

Z_weighted = Z * W
A_pos = Z_weighted.max(axis=0)
A_neg = Z_weighted.min(axis=0)

D_pos = np.sqrt(((Z_weighted - A_pos) ** 2).sum(axis=1))
D_neg = np.sqrt(((Z_weighted - A_neg) ** 2).sum(axis=1))

pot["potential_score"] = D_neg / (D_pos + D_neg)
pot_rank = pot.sort_values("potential_score", ascending=False)

pot_rank.to_excel("C_其他省份举办潜力排名.xlsx", index=False)

 

 

 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐