当前位置: 首页 > news >正文

用Python的OR-Tools搞定日历拼图:保姆级建模与求解教程(附完整代码)

用Python的OR-Tools搞定日历拼图:保姆级建模与求解教程(附完整代码)

日历拼图是一种将特定形状的拼图块填入带有日期标记的底板中的智力游戏。这类问题看似简单,实则涉及复杂的空间排列组合,非常适合用数学建模和优化求解的方法来解决。本文将带你从零开始,使用Google的OR-Tools工具包中的CP-SAT求解器,一步步构建并求解日历拼图问题。

1. 环境准备与问题分析

1.1 安装必要的Python库

首先确保你的Python环境已经安装了以下库:

pip install ortools numpy matplotlib

OR-Tools是Google开发的开源优化工具包,其中CP-SAT求解器特别适合解决这类组合优化问题。我们将使用它来建模和求解拼图问题。

1.2 理解日历拼图的基本规则

日历拼图通常由以下几部分组成:

  • 一个7x7的底板,其中包含月份、星期和日期的标记
  • 10块不同形状的拼图块,每块由4-5个小方块组成
  • 拼图块可以旋转和翻转,产生不同的形态

我们的目标是将这些拼图块填入底板,覆盖所有非日期标记的位置,同时不重叠且完全匹配底板形状。

2. 建立数学模型

2.1 定义决策变量

我们需要为每个拼图块的每个可能的放置方式创建一个二元变量:

from ortools.sat.python import cp_model model = cp_model.CpModel() # 定义拼图块数量、形态数、行列数 num_pieces = 10 num_states = 8 # 原始形态+7种旋转/翻转 rows = 7 cols = 7 # 创建决策变量:work[p,s,r,c]表示拼图块p在形态s下是否以(r,c)为基准点放置 work = {} for p in range(num_pieces): for s in range(num_states): for r in range(rows): for c in range(cols): work[p,s,r,c] = model.NewBoolVar(f'work_{p}_{s}_{r}_{c}')

2.2 设置基本约束条件

每个拼图块必须且只能以一种形态放置在一个位置:

# 每个拼图块只能选择一种形态和一个位置 for p in range(num_pieces): model.AddExactlyOne(work[p,s,r,c] for s in range(num_states) for r in range(rows) for c in range(cols))

底板上的每个位置最多只能被一个拼图块覆盖:

# 预定义底板形状 - 这里用0表示空白,1表示需要填充的位置 board = [ [0,0,1,1,1,0,0], [0,1,1,1,1,1,0], [1,1,1,1,1,1,1], [1,1,1,1,1,1,1], [1,1,1,1,1,1,1], [0,1,1,1,1,1,0], [0,0,1,1,1,0,0] ] # 确保每个需要填充的位置只被一个拼图块覆盖 for r in range(rows): for c in range(cols): if board[r][c] == 1: model.AddAtMostOne( work[p,s,r-i,c-j] for p in range(num_pieces) for s in range(num_states) for (i,j) in get_offsets(p,s) # 获取拼图块在形态s下的偏移量 if 0 <= r-i < rows and 0 <= c-j < cols )

3. 拼图形状与变换处理

3.1 定义拼图块的原始形状

我们需要为每个拼图块定义其原始形状的方块位置:

pieces = [ # 每个拼图块定义为其小方块的相对坐标[(i,j)] [(0,0),(0,1),(0,2),(1,0),(1,1)], # 拼图块0 [(0,0),(0,1),(1,1),(2,1),(2,2)], # 拼图块1 # ...其他拼图块定义 ]

3.2 实现拼图块的旋转和翻转

拼图块可以通过旋转和翻转产生不同的形态:

def get_transformed_piece(piece, state): """根据状态编号返回变换后的拼图形状""" transformed = [] for (i,j) in piece: if state == 0: # 原始形态 ni, nj = i, j elif state == 1: # 旋转90度 ni, nj = j, -i # ...其他变换状态 transformed.append((ni,nj)) return transformed def get_offsets(p, s): """返回拼图块p在形态s下的所有方块相对于基准点的偏移""" piece = pieces[p] transformed = get_transformed_piece(piece, s) return transformed

4. 完整求解与结果可视化

4.1 设置求解器并执行求解

solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: print("找到解!") # 处理解... else: print("未找到解")

4.2 解析并可视化结果

我们可以将求解结果转换为可视化的拼图布局:

import matplotlib.pyplot as plt import numpy as np def visualize_solution(solution): fig, ax = plt.subplots(figsize=(8,8)) # 绘制底板 for r in range(rows): for c in range(cols): if board[r][c] == 0: ax.add_patch(plt.Rectangle((c,-r), 1, 1, color='lightgray')) # 绘制拼图块 colors = plt.cm.tab10.colors for p in range(num_pieces): for s in range(num_states): for r in range(rows): for c in range(cols): if solver.Value(work[p,s,r,c]): offsets = get_offsets(p,s) for (i,j) in offsets: ax.add_patch(plt.Rectangle( (c+j,-(r+i)), 1, 1, color=colors[p], alpha=0.7 )) ax.set_xlim(0, cols) ax.set_ylim(-rows, 0) ax.set_aspect('equal') ax.axis('off') plt.show() visualize_solution(solver)

5. 高级技巧与优化

5.1 处理特定日期的约束

要解决特定日期的拼图问题,我们需要添加额外的约束:

def add_date_constraint(model, work, month, day, weekday): # 月份约束 (1-12) month_positions = [(0,2),(0,3),(0,4)] # 示例位置 for r,c in month_positions: model.Add(sum( work[p,s,r-i,c-j] for p in range(num_pieces) for s in range(num_states) for (i,j) in get_offsets(p,s) if (r-i,c-j) == (r,c) ) == month) # 类似添加日期和星期的约束...

5.2 对称性消除

拼图问题通常有很多对称解,我们可以添加约束来消除对称性,加速求解:

# 强制某些拼图块按特定顺序放置,消除对称解 for p in range(1, num_pieces): model.Add( sum(r*cols + c for s in range(num_states) for r in range(rows) for c in range(cols) if solver.Value(work[p,s,r,c])) > sum(r*cols + c for s in range(num_states) for r in range(rows) for c in range(cols) if solver.Value(work[p-1,s,r,c])) )

5.3 性能优化技巧

对于大型拼图或需要求解多个解的情况,可以考虑以下优化:

  • 限制求解时间:solver.parameters.max_time_in_seconds = 60
  • 使用多线程:solver.parameters.num_search_workers = 4
  • 添加启发式规则:优先放置较大或形状特殊的拼图块
# 优化求解器参数 solver.parameters.max_time_in_seconds = 30 # 限制求解时间 solver.parameters.num_search_workers = 4 # 使用4个线程

在实际项目中,我发现将较大的拼图块优先放置可以显著减少求解时间。另外,合理设置对称性消除约束可以避免求解器浪费时间在本质上相同的解上。

http://www.jsqmd.com/news/765900/

相关文章:

  • 装修入门必看:前期准备全梳理
  • Jetson Nano内核编译避坑实录:从权限错误到LSE atomics,我踩过的那些雷
  • 抖音视频怎么去水印?抖音去水印工具推荐,2026亲测可用的几种方法 - 科技热点发布
  • RPG Maker MV/MZ游戏资源解密工具:Java版完全使用指南
  • 基于深度学习的水下目标检测系统(YOLOv12完整代码+论文示例+多算法对比)
  • 免费修复机械键盘连击:KeyboardChatterBlocker终极使用指南
  • 别再手动整理了!用Python一键抓取并生成全国银行简码JSON数据(附完整代码)
  • 终极指南:如何突破群晖NAS硬盘兼容性限制,自由选择第三方存储设备
  • 泉盛UV-K5/K6对讲机固件终极解析:从开源定制到专业级通信系统
  • 深入Linux触摸屏:从ABS_MT_SLOT到多点触控事件解析实战
  • Debian 12 + VMware 17保姆级配置:从换清华源到装多版本JDK,一条龙搞定开发环境
  • 探索Taotoken模型广场如何辅助开发者进行技术选型与测试
  • 基于秒悟低代码平台户外活动H5应用开发
  • ChanlunX缠论插件终极指南:通达信自动笔段中枢识别完整教程
  • 小红书去水印下载工具哪个好用?2026年免费安全的去水印工具推荐 - 科技热点发布
  • 利用快马平台与codex cli快速构建ai驱动命令行工具原型
  • 实测Taotoken聚合端点在高峰时段的请求稳定性与延迟表现
  • CDecrypt:如何高效解密Wii U游戏文件的技术指南
  • 告别复杂配置:用快马AI生成脚本,秒速实现本地服务公网调试
  • 探索Taotoken模型广场如何帮助开发者快速进行模型选型
  • 创业团队如何利用 Taotoken 多模型能力快速验证 AI 产品原型
  • 【独家逆向分析】VSCode 2026协作协议栈拆解:LSP v4.2 + CRDT+ WebSocket 3.1三重融合,延迟压至≤87ms(附性能压测数据表)
  • Harvester网络管理完全指南:VIP、VLAN与多网卡配置实战
  • 一键去水印在线怎么操作?在线去水印网站推荐,2026实测有效方法汇总 - 科技热点发布
  • 别再死记硬背奈奎斯特定理了!用Python+Arduino动手玩转ADC采样,直观理解混叠现象
  • 406. 根据身高重建队列
  • 48岁老程序员被大厂裁员,存款7位数,社保交够20年了,回县城吃利息等60岁领退休金
  • XCP协议不止于CAN:手把手带你用Wireshark抓包分析Ethernet上的标定通信
  • 从勒索攻击到零信任落地,MCP 2026强制要求的4大技术基线,你医院的HIS系统达标了吗?
  • 免费视频去水印在线工具有哪些?2026实测推荐,视频去水印在线工具怎么选? - 科技热点发布