模拟退火(Simulated Annealing)算法 PO J1379 2420

本文介绍了模拟退火算法的基本原理及其在解决最远、最近或第k近距离问题中的应用。通过两个具体信息学竞赛题目,详细展示了算法实现过程,包括初始化、迭代更新以及终止条件等关键步骤。

在很多信息学竞赛选手看来,很多时候几何题目就是代码量大的代名词,即使对于一些经典问题,庞大的代码量也使很多人望而却步。模拟退火算法思维及编写简单、灵活,可以在一类最远、最近或第k近距离问题中发挥威力。

模拟退火算法介绍

1.1 模拟退火算法的原理

模拟退火算法是一种元启发式(Meta-Heuristics)算法,来源于固体退火原理,将固体加热至充分高的温度,再让其徐徐冷却。加热时,固体内部粒子随温升变为无序状,内能增大,而徐徐冷却时粒子渐趋有序,在每个温度都达到平衡态,最后在常温时达到基态,内能减为最小。根据Metropolis准则,粒子在温度T时趋于平衡的概率为 ,其中E为温度T时的内能,ΔE为其改变量,kBoltzmann常数。

1.2 模拟退火算法的模型

① 初始化:初始温度T(充分大),初始解状态S(算法迭代的起点), 每次迭代次数L

for k=1 to L 做③至⑥

③ 产生新解S’

④ 计算增量Δt′=C(S′)-C(S),其中C(S)为评价函数

⑤ 若Δt′<0则接受S’作为新的当前解,否则以概率 接受S’作为新的当前解

⑥ 如果满足终止条件则输出当前解作为最优解,结束程序

T逐渐减少,然后转②

 

POJ2420
A Star not a Tree?
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 1335 Accepted: 696

Description

Luke wants to upgrade his home computer network from 10mbs to 100mbs. His existing network uses 10base2 (coaxial) cables that allow you to connect any number of computers together in a linear arrangement. Luke is particulary proud that he solved a nasty NP-complete problem in order to minimize the total cable length.
Unfortunately, Luke cannot use his existing cabling. The 100mbs system uses 100baseT (twisted pair) cables. Each 100baseT cable connects only two devices: either two network cards or a network card and a hub. (A hub is an electronic device that interconnects several cables.) Luke has a choice: He can buy 2N-2 network cards and connect his N computers together by inserting one or more cards into each computer and connecting them all together. Or he can buy N network cards and a hub and connect each of his N computers to the hub. The first approach would require that Luke configure his operating system to forward network traffic. However, with the installation of Winux 2007.2, Luke discovered that network forwarding no longer worked. He couldn't figure out how to re-enable forwarding, and he had never heard of Prim or Kruskal, so he settled on the second approach: N network cards and a hub.

Luke lives in a loft and so is prepared to run the cables and place the hub anywhere. But he won't move his computers. He wants to minimize the total length of cable he must buy.

Input

The first line of input contains a positive integer N <= 100, the number of computers. N lines follow; each gives the (x,y) coordinates (in mm.) of a computer within the room. All coordinates are integers between 0 and 10,000.

Output

Output consists of one number, the total length of the cable segments, rounded to the nearest mm.

Sample Input

4
0 0
0 10000
10000 10000
10000 0

Sample Output

28284

Source

 

求一点到多边形各顶点距离和的最小值(费马点)

 

POJ1379

Run Away
Time Limit: 3000MS Memory Limit: 65536K
Total Submissions: 2000 Accepted: 561

Description

One of the traps we will encounter in the Pyramid is located in the Large Room. A lot of small holes are drilled into the floor. They look completely harmless at the first sight. But when activated, they start to throw out very hot java, uh ... pardon, lava. Unfortunately, all known paths to the Center Room (where the Sarcophagus is) contain a trigger that activates the trap. The ACM were not able to avoid that. But they have carefully monitored the positions of all the holes. So it is important to find the place in the Large Room that has the maximal distance from all the holes. This place is the safest in the entire room and the archaeologist has to hide there.

Input

The input consists of T test cases. The number of them (T) is given on the first line of the input file. Each test case begins with a line containing three integers X, Y, M separated by space. The numbers satisfy conditions: 1 <= X,Y <=10000, 1 <= M <= 1000. The numbers X and Yindicate the dimensions of the Large Room which has a rectangular shape. The number M stands for the number of holes. Then exactly M lines follow, each containing two integer numbers Ui and Vi (0 <= Ui <= X, 0 <= Vi <= Y) indicating the coordinates of one hole. There may be several holes at the same position.

Output

Print exactly one line for each test case. The line should contain the sentence "The safest point is (P, Q)." where P and Qare the coordinates of the point in the room that has the maximum distance from the nearest hole, rounded to the nearest number with exactly one digit after the decimal point (0.05 rounds up to 0.1).

Sample Input

3
1000 50 1
10 10
100 100 4
10 10
10 90
90 10
90 90
3000 3000 4
1200 85
63 2500
2700 2650 
2990 100

Sample Output

The safest point is (1000.0, 50.0).
The safest point is (50.0, 50.0).
The safest point is (1433.0, 1669.8).

Source

import pandas as pd import numpy as np from datetime import datetime, timedelta import math import random import copy import os class OptimizedProductionScheduler: def __init__(self, orders_file_path, product_machine_file_path): # 初始化基础参数 self.base_date = datetime(2025, 2, 25, 0, 0, 0) self.holidays = [ datetime(2025, 2, 20), datetime(2025, 3, 6), datetime(2025, 3, 20), datetime(2025, 4, 3), datetime(2025, 4, 17), datetime(2025, 5, 1), datetime(2025, 5, 2), datetime(2025, 5, 3), datetime(2025, 5, 4), datetime(2025, 5, 15), datetime(2025, 5, 29), datetime(2025, 6, 12), datetime(2025, 6, 26) ] self.changeover_time = 3.5 # 换模时间(小时) self.daily_work_hours = 22 # 每日有效工作小时 # 模拟退火参数 self.initial_temperature = 1000 self.cooling_rate = 0.95 self.max_iterations = 500 self.max_no_improve = 50 # 存储Excel文件路径(由用户传入) self.orders_file = orders_file_path self.product_machine_file = product_machine_file_path # 初始化数据容器 self.orders_df = None self.product_machines = None self.orders = None self.all_machines = None self.original_plan_numbers = set() # 存储原始计划序号 def read_orders_from_excel(self): """从Excel读取订单数据,返回DataFrame""" try: df = pd.read_excel(self.orders_file,engine='openpyxl') # 检查必要列是否存在 required_columns = ['计划序号', '订单号PO', '产品号', 'Weight/pcs(g)', 'Capacity(pcs/h)', '订单数量', '发货日期(DeliveryDate)'] if not set(required_columns).issubset(df.columns): missing = set(required_columns) - set(df.columns) raise ValueError(f"订单数据Excel缺少必要列:{missing}") # 保存原始计划序号 self.original_plan_numbers = set(df['计划序号'].tolist()) # 转换发货日期为datetime格式(修复警告) df['发货日期'] = pd.to_datetime(df['发货日期(DeliveryDate)']).apply( lambda x: x.to_pydatetime() ) return df except Exception as e: raise RuntimeError(f"读取订单数据失败:{str(e)}") def read_product_machines_from_excel(self): """从Excel读取产品-机器映射,返回字典""" try: df = pd.read_excel(self.product_machine_file,engine='openpyxl') # 检查产品号列是否存在 if '产品号' not in df.columns: raise ValueError("产品-机器映射Excel缺少'产品号'列") product_machines = {} # 机器列是除了'产品号'之外的所有列 machine_columns = [col for col in df.columns if col != '产品号'] self.all_machines = machine_columns # 所有机器列表(从表头提取) for _, row in df.iterrows(): product_id = row['产品号'] # 收集值为1的机器(可生产该产品的机器) available_machines = [machine for machine in machine_columns if row[machine] == 1] product_machines[product_id] = available_machines return product_machines except Exception as e: raise RuntimeError(f"读取产品-机器映射失败:{str(e)}") def load_data(self): """加载数据(从Excel读取,替代硬编码)""" print("从Excel加载订单数据...") self.orders_df = self.read_orders_from_excel() print("从Excel加载产品-机器映射...") self.product_machines = self.read_product_machines_from_excel() # 转换订单数据为字典列表(供算法使用) self.orders = self.orders_df.to_dict('records') def is_holiday(self, date_obj): """判断日期是否为节假日""" return date_obj.date() in [h.date() for h in self.holidays] def convert_to_real_time_fast(self, start_hours, duration_hours): """快速时间转换(考虑节假日和工作时长)""" total_days = start_hours / 24 work_days = duration_hours / self.daily_work_hours # 计期间的节假日数量 current_date = self.base_date + timedelta(days=int(total_days)) holiday_count = sum(1 for i in range(int(work_days) + 2) if self.is_holiday(current_date + timedelta(days=i))) # 总耗时 = 工作耗时 + 节假日天数×24小时(节假日不工作) total_hours = start_hours + duration_hours + holiday_count * 24 return total_hours def evaluate_solution(self, solution): """评估解决方案的成本(总延迟时间)""" total_delay = 0 machine_times = {machine: 0 for machine in self.all_machines} # 机器当前可用时间 machine_last_product = {machine: None for machine in self.all_machines} # 机器上一个生产的产品 for order_idx, machine in enumerate(solution): order = self.orders[order_idx] product = order['产品号'] # 计开始时间(考虑换模时间) start_time = machine_times[machine] if machine_last_product[machine] != product: start_time += self.changeover_time # 换模时间 # 计加工时间和完成时间 process_time = order['订单数量'] / order['Capacity(pcs/h)'] finish_time = self.convert_to_real_time_fast(start_time, process_time) # 计延迟时间(若完成时间晚于发货日期) delivery_hours = (order['发货日期'] - self.base_date).total_seconds() / 3600 delay = max(0, finish_time - delivery_hours) total_delay += delay # 更新机器状态 machine_times[machine] = finish_time machine_last_product[machine] = product return total_delay def generate_initial_solution(self): """生成初始解(随机分配可用机器)""" solution = [] for order in self.orders: available_machines = self.product_machines.get(order['产品号'], []) if available_machines: solution.append(random.choice(available_machines)) else: # 无可用机器时随机分配一台 solution.append(random.choice(self.all_machines)) return solution def get_neighbor(self, current_solution): """生成邻域解(微调当前解)""" neighbor = current_solution.copy() mutation_type = random.choice(['swap', 'change_machine', 'reschedule']) if mutation_type == 'swap' and len(self.orders) > 1: # 交换两个订单的机器 i, j = random.sample(range(len(self.orders)), 2) neighbor[i], neighbor[j] = neighbor[j], neighbor[i] elif mutation_type == 'change_machine': # 为单个订单更换机器 idx = random.randint(0, len(self.orders) - 1) order = self.orders[idx] available_machines = self.product_machines.get(order['产品号'], self.all_machines) neighbor[idx] = random.choice(available_machines) elif mutation_type == 'reschedule': # 重新分配到可用机器的第一个 idx = random.randint(0, len(self.orders) - 1) order = self.orders[idx] available_machines = self.product_machines.get(order['产品号'], self.all_machines) neighbor[idx] = available_machines[0] if available_machines else self.all_machines[0] return neighbor def simulated_annealing(self): """模拟退火算法寻找最优解""" current_solution = self.generate_initial_solution() current_cost = self.evaluate_solution(current_solution) best_solution = current_solution.copy() best_cost = current_cost temperature = self.initial_temperature no_improve_count = 0 print("开始模拟退火优化...") print(f"初始成本: {current_cost:.2f}(总延迟小时)") for iteration in range(self.max_iterations): if no_improve_count >= self.max_no_improve: print(f"提前终止:连续{self.max_no_improve}次无改进") break neighbor = self.get_neighbor(current_solution) neighbor_cost = self.evaluate_solution(neighbor) cost_diff = neighbor_cost - current_cost # 接受更优解,或一定概率接受较差解 if cost_diff < 0 or random.random() < math.exp(-cost_diff / temperature): current_solution = neighbor current_cost = neighbor_cost if current_cost < best_cost: best_solution = current_solution.copy() best_cost = current_cost no_improve_count = 0 print(f"迭代 {iteration}: 新最优成本 = {best_cost:.2f}") else: no_improve_count += 1 # 降温 temperature *= self.cooling_rate if iteration % 100 == 0: print(f"迭代 {iteration}: 温度 = {temperature:.2f}, 当前成本 = {current_cost:.2f}") print(f"优化完成!最优成本: {best_cost:.2f}(总延迟小时)") return best_solution, best_cost def decode_solution(self, solution): """将最优解转换为详细排程表,保留原始计划序号""" detailed_schedule = [] machine_times = {machine: 0 for machine in self.all_machines} machine_last_product = {machine: None for machine in self.all_machines} machine_previous_plan = {machine: None for machine in self.all_machines} # 确定新计划序号的起始值(原始最大序号 + 1) max_original_plan = max(self.original_plan_numbers) if self.original_plan_numbers else 0 new_plan_counter = max_original_plan + 1 for order_idx, machine in enumerate(solution): order = self.orders[order_idx] product = order['产品号'] # 计开始时间(含换模时间) start_time = machine_times[machine] if machine_last_product[machine] != product: start_time += self.changeover_time # 计加工时间和实际时间 process_time = order['订单数量'] / order['Capacity(pcs/h)'] finish_time = self.convert_to_real_time_fast(start_time, process_time) start_datetime = self.base_date + timedelta(hours=start_time) finish_datetime = self.base_date + timedelta(hours=finish_time) # 计延迟和满意度 delivery_date = order['发货日期'] if finish_datetime <= delivery_date: delay_days = 0 satisfaction = 10.0 else: delay_days = (finish_datetime - delivery_date).days satisfaction = max(0, 10.0 - 0.1 * math.ceil(delay_days / 3)) # 生成排程记录 - 使用原始计划序号 plan_record = { '计划序号': order['计划序号'], # 关键修改:使用订单本身的计划序号 '生产计划安排机台号': machine, '订单号PO': order['订单号PO'], '产品号': product, '需要工时': round(process_time, 2), '生产计划开始时间': start_datetime, '生产计划预计完成时间': finish_datetime, '发货日期': delivery_date, '订单数量': order['订单数量'], 'Weight/pcs(g)': order['Weight/pcs(g)'], 'Capacity(pcs/h)': order['Capacity(pcs/h)'], '最迟开始时间': delivery_date - timedelta(hours=process_time), '前置任务生产计划序号': machine_previous_plan[machine], '是否延期': delay_days > 0, '延期天数': delay_days, '满意度': satisfaction } detailed_schedule.append(plan_record) # 更新机器状态 machine_times[machine] = finish_time machine_last_product[machine] = product machine_previous_plan[machine] = order['计划序号'] # 使用原始计划序号作为前置任务引用 # 按计划序号排序 detailed_schedule.sort(key=lambda x: x['计划序号']) return detailed_schedule def run_optimized(self): """运行完整优化流程""" try: self.load_data() # 从Excel加载数据 print("运行模拟退火算法...") best_solution, best_cost = self.simulated_annealing() print("解码最优解...") detailed_schedule = self.decode_solution(best_solution) # 计统计指标 delayed_orders = sum(1 for plan in detailed_schedule if plan['是否延期']) max_delay = max(plan['延期天数'] for plan in detailed_schedule) if detailed_schedule else 0 avg_delay = (sum(plan['延期天数'] for plan in detailed_schedule) / len(detailed_schedule) if detailed_schedule else 0) avg_satisfaction = (sum(plan['满意度'] for plan in detailed_schedule) / len(detailed_schedule) if detailed_schedule else 0) min_satisfaction = min(plan['满意度'] for plan in detailed_schedule) if detailed_schedule else 0 stats = { '延期订单总数': delayed_orders, '最长延期天数': max_delay, '平均延期天数': round(avg_delay, 1), '订单平均满意度': round(avg_satisfaction, 2), '订单最差满意度': round(min_satisfaction, 2) } # 生成输出文件(使用桌面路径避免权限问题) #desktop_path = os.path.join(os.path.expanduser("~"), "Desktop") #if not os.path.exists(desktop_path): desktop_path = "." # 若桌面路径不存在,使用当前目录 schedule_df = pd.DataFrame(detailed_schedule) schedule_path = os.path.join(desktop_path, '附件3_优化生产排程表.xlsx') schedule_df.to_excel(schedule_path, index=False) stats_df = pd.DataFrame([stats]) stats_path = os.path.join(desktop_path, '问题1_优化统计结果.xlsx') stats_df.to_excel(stats_path, index=False) print("=" * 50) print("优化完成!") print(f"排程表已保存至: {schedule_path}") print(f"统计结果已保存至: {stats_path}") print("=" * 50) print("统计结果:") for key, value in stats.items(): print(f"{key}: {value}") return detailed_schedule, stats except Exception as e: print(f"运行失败:{str(e)}") return None, None # 运行优化(需传入Excel文件路径) if __name__ == "__main__": # 替换为实际的Excel文件路径 ORDERS_FILE ="111.xlsx" # 订单数据Excel路径 PRODUCT_MACHINE_FILE ="222.xlsx" # 产品-机器映射Excel路径 optimizer = OptimizedProductionScheduler(orders_file_path=ORDERS_FILE, product_machine_file_path=PRODUCT_MACHINE_FILE) detailed_schedule, stats = optimizer.run_optimized() 分析代码啥意思
最新发布
08-21
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值