保龄球协会赛季赛程自动规划方案开发技术咨询
保龄球协会全赛季赛程规划:优化目标与实现起点
一、确定优化目标与核心约束
硬性约束(必须满足)
- 排除12月及场馆已预订日期
- 每队赛季参赛6次(即每个联赛需安排6场比赛)
- 相邻比赛日间隔为3或4周
- 大馆最多同时容纳2个联赛,其余2个场馆各容纳1个
优化目标(优先级从高到低)
- 最大化大馆利用率:尽可能每次安排2个联赛,减少场地闲置
- 均衡联赛赛程分布:让每个联赛的比赛日尽可能均匀间隔,避免扎堆
- 均衡场馆使用频次:大馆总使用场次约为其他两馆的2倍(因单次容量为2)
二、OpenOffice Calc 实现步骤
你已整理好可用日期,接下来按以下框架搭建:
1. 整理基础数据表
日期表:列出所有合规日期(排除12月/已预订),新增周间隔列,用DATEDIF(上一日期, 当前日期, "W")计算与上一可用日期的周差,筛选出差值为3或4的日期序列联赛清单:7个联赛,每个联赛标记6支队伍的参赛次数需求(固定为6)场馆清单:标记大馆(容量2)和普通馆(容量1)
2. 用规划求解(Solver)做赛程分配
OpenOffice的规划求解是核心工具,配置如下:
- 目标单元格:计算大馆有效使用率(每安排2个联赛计1分,安排1个计0.5分),设置为
最大化 - 变量单元格:创建二维表(行=合规日期,列=联赛),单元格值为
0/1(0=不安排,1=安排);另建表匹配日期-联赛对应的场馆 - 约束条件:
- 每个联赛对应的行总和=6(确保赛季6场比赛)
- 单日期内,大馆对应的联赛数≤2,普通馆≤1
- 每个联赛的相邻比赛日周差为3或4(用
DATEDIF函数验证后添加约束) - 所有日期不在禁用列表内
3. 用函数验证约束
用COUNTIF、SUMIF实时校验:
SUMIF(赛程表!A:A, "联赛1", 赛程表!B:B):验证联赛1的总比赛次数是否为6DATEDIF(INDEX(赛程表!A:A, MATCH("联赛1", 赛程表!A:A, 0)), 下一个比赛日期, "W"):验证间隔合规性
三、Python 实现起点
如果Calc的规划求解灵活性不足,用Python的整数规划或启发式算法更高效:
1. 依赖库选择
- 整数规划:
pulp(轻量级,易上手)或ortools(适合复杂约束) - 日期处理:
datetime
2. 核心代码示例
(1)初始化数据
from datetime import datetime, timedelta import pulp # 替换为你的日期列表和已预订日期 date_list = [datetime(2024,1,7), datetime(2024,1,28), ...] # 示例日期 booked_dates = [datetime(2024,2,18)] # 已预订日期 # 筛选合规日期:排除12月、已预订,且相邻间隔3/4周 available_dates = [] prev_date = None for d in sorted(date_list): if d.month == 12 or d in booked_dates: continue if prev_date: weeks_diff = (d - prev_date).days // 7 if weeks_diff not in (3,4): continue available_dates.append(d) prev_date = d leagues = [f"League_{i}" for i in range(1,8)] venues = {"Big_Venue": 2, "Venue_A":1, "Venue_B":1} # 场馆:容量
(2)构建优化模型
# 创建最大化问题 prob = pulp.LpProblem("BowlingSchedule", pulp.LpMaximize) # 变量:(日期, 联赛, 场馆) = 1 表示该组合生效 schedule_vars = pulp.LpVariable.dicts( "Schedule", [(d, l, v) for d in available_dates for l in leagues for v in venues], cat="Binary" ) # 目标函数:最大化大馆使用率(满负荷计1分,半负荷计0.5分) prob += pulp.lpSum([schedule_vars[(d,l,"Big_Venue")] for d,l in [(d,l) for d in available_dates for l in leagues]]) * 0.5 # 约束1:每个联赛必须安排6场 for l in leagues: prob += pulp.lpSum([schedule_vars[(d,l,v)] for d in available_dates for v in venues]) == 6 # 约束2:单日期单场馆容量不超限 for d in available_dates: for v, cap in venues.items(): prob += pulp.lpSum([schedule_vars[(d,l,v)] for l in leagues]) <= cap # 约束3:联赛相邻比赛间隔3/4周 for l in leagues: # 遍历所有日期对,确保选中的相邻日期间隔合规 for i in range(len(available_dates)): for j in range(i+1, len(available_dates)): d1 = available_dates[i] d2 = available_dates[j] weeks_diff = (d2 - d1).days //7 if weeks_diff not in (3,4): # 若间隔不合规,不能同时选中这两个日期作为该联赛的比赛日 prob += schedule_vars[(d1,l,"Big_Venue")] + schedule_vars[(d1,l,"Venue_A")] + schedule_vars[(d1,l,"Venue_B")] + \ schedule_vars[(d2,l,"Big_Venue")] + schedule_vars[(d2,l,"Venue_A")] + schedule_vars[(d2,l,"Venue_B")] <= 1
(3)求解并输出结果
prob.solve(pulp.PULP_CBC_CMD(msg=0)) # 静默求解 # 输出最终赛程 print("最终赛程:") for var in prob.variables(): if var.varValue == 1: # 解析变量名 parts = var.name.split("_") date_str = f"{parts[1]}-{parts[2]}-{parts[3]}" league = parts[4] venue = "_".join(parts[5:]) print(f"日期:{date_str} | 联赛:{league} | 场馆:{venue}")
3. 备选:贪心算法快速生成初始方案
如果整数规划求解耗时,可先用贪心算法生成初始赛程再调整:
- 给每个联赛分配6个符合间隔要求的日期
- 对每个日期,优先将联赛分配到大馆,填满后再分配普通馆
- 检查冲突(如场馆容量超限),调整日期或场馆分配
内容的提问来源于stack exchange,提问作者marie1611
相关产品推荐
相关产品推荐

