前言
"全球校园人工智能算法精英大赛"是江苏省人工智能学会举办的面向全球具有正式学籍的全日制高等院校及以上在校学生举办的算法竞赛。其中的算法巅峰赛属于产业命题赛道,这是第3赛季,这次优化题的主题是 "碳中和"。
题目描述
给你 N 个任务 和 M 台服务器,需要决定:
- 哪些任务要执行
- 每个执行的任务分配到哪台服务器
核心约束
- 功耗约束
每台服务器的实际总功耗 ≤ 该服务器的功耗上限 - 热量约束
每台服务器的实际承受总热量 ≤ 该服务器的热量阈值 - 热量传导规则
服务器排成一排,相邻服务器会互相传导热量:
怎么理解热量约束和热量传达规则
服务器 p 承受的热量 = 服务器 p 任务的热量总和 + 左邻热量和 × K + 右邻热量和 × K 服务器 p 承受的热量 = 服务器p 任务的热量总和 + 左邻热量和 × K + 右邻热量和 × K 服务器p承受的热量=服务器p任务的热量总和+左邻热量和×K+右邻热量和×K
注: 左右两侧服务器的特殊情况,非环。
核心目标
在满足所有限制条件下,最大化总收益。
输入数据
第一行
N M K
N:任务数量(1-4000)
M:服务器数量(1-800)
K:热量传导系数(0.0-1.0)
任务数据(N行,每行3个数字)
收益 功耗 热量
服务器数据(M行,每行2个数字)
功耗上限 热量阈值
输出格式
输出 N 个数字,用空格分隔:
a_1, a_2, a_3, ..., a_n
a i ∈ [ 0 , m ] a_i \in [0, m] ai∈[0,m] 其中0: 该任务i不执行, 1-M: 该任务 i分配到几号服务器
sample
输入
3 2 1.0
10 3 4
8 4 3
5 2 2
5 7
7 8
输出
1 2 0
解释
有 3 个任务,2 台服务器,K=1.0(100%传导)
服务器1:最多用5功耗,承受7热量
服务器2:最多用7功耗,承受8热量
任务1(功耗3,热量4)→ 服务器1
任务2(功耗4,热量3)→ 服务器2
任务3不执行
检查约束:
服务器1:功耗3 ≤ 5 ✅,热量4 ≤ 7 ✅
服务器2:功耗4 ≤ 7 ✅,热量3 ≤ 8 ✅
考虑传导:
服务器1总热量:自己4 + 邻居传导3×1.0 = 7(刚好等于阈值7)✅
服务器2总热量:自己3 + 邻居传导4×1.0 = 7 ≤ 8 ✅
总收益:10 + 8 = 18 ✅
思路分析
该问题属于带约束的多维度资源分配优化问题,是组合优化问题。
该问题可以视为 二维费用的 0-1 多重背包的变形问题。
高分思路
贪心策略
- 任务task按照收益从高到低排序
- 按需为每个 task寻找第一个满足约束限制的服务器 server 节点,进行分配
python3
# task按收益从高到低排序
tasks.sort(key=lambda x: -x.val)
# 遍历每个任务
for task in tasks:
for server in servers:
# 如果该服务器,添加该任务后,依旧满足约束
if server.satisfy(task):
# 该 task 被赋予给 该 server
task -> server
break
就这么一个简单的思路,最后分数为 400+,赛时前 15 名。
多策略混合
保留原先的贪心策略主框架,做一些策略抽象
python3
sort_stragety #排序策略
choice_strategy # 挑选策略
# task按 策略 sort_strategy 排序
tasks.sort(key=sort_stragety)
# 遍历每个任务
for task in tasks:
candidate_list = []
for server in servers:
# 如果该服务器,添加该任务后,依旧满足约束
if server.satisfy(task):
candidate_list.append(server)
# 如果存在候选服务器列表
if candidate_list:
# 按挑选策略,选择具体的svr
svr = choice_strategy(candidate_list)
task -> svr # 把 task 赋予某个 server 执行
这里抽象了 2 个策略,sort_stragety/choice_strategy
任务的排序策略
- 按收益值从高到低 排序
- 按收益/功耗 比值,从高到低 排序
- 按收益/热度 比值,从高到低 排序
- 按收益/功耗热度的比值,从高到低 排序
服务器的挑选策略
- FF(First-Fit)
选择第一个满足要求服务器 - BF(Best-Fit)
按最优策略,比如定义 加权 功耗利用率 + 热量利用率的函数,越高越好 - RF(Random-Fit)
随机挑选任一个满足要求的服务器 - 概率放弃
按一定概率放弃此次选择任务和服务器配对,否则采用上述几种策略挑选
而任务的排序策略+服务器的挑选策略,做一个组合搭配。
这样的混合策略得分为 440+,赛时前三。
模拟退火
模拟退火也可以做这题,但操作因子设计就很复杂,不太好实现。
这边以最简单的任务重分配操作因子 为例(纯展示)
python
import math, random
from math import inf
class Task(object):
def __init__(self, value, power, heat):
self.value = value
self.power = power
self.heat = heat
class Server(object):
def __init__(self, p_limit, h_limit):
self.p_limit = p_limit
self.h_limit = h_limit
N, M, K = input().split()
N, M, K = int(N), int(M), float(K)
tasks = []
for _ in range(N):
value, power, heat = list(map(int, input().split()))
tasks.append(Task(value, power, heat))
servers = []
for _ in range(M):
p_limit, h_limit = list(map(int, input().split()))
servers.append(Server(p_limit, h_limit))
def fitness(assigns):
ans = 0
tmp_power = [0] * M
tmp_heat = [0] * M
for i in range(N):
id = assigns[i]
if id >= 0:
tmp_power[id] += tasks[i].power
tmp_heat[id] += tasks[i].heat
ans += tasks[i].value
# check
for i in range(M):
if tmp_power[i] > servers[i].p_limit:
return -inf
heat = tmp_heat[i]
if i > 0: heat += tmp_heat[i - 1] * K
if i + 1 < M: heat += tmp_heat[i + 1] * K
if heat > servers[i].h_limit:
return -inf
return ans
assigns = [-1] * N
current_score = 0
best_score = 0
best_ans = assigns[:]
a = 0.999
T = 1000
while T > 1e-6:
# reassign
idx = random.randint(0, N - 1)
old = assigns[idx]
assigns[idx] = random.randint(-1, M - 1) #
score = fitness(assigns)
if score == -inf:
# 不合法的方案立马拒绝
assigns[idx] = old
T = T * a
continue
delta = score - current_score
if delta > 0 or random.random() < math.exp(delta / T):
# accept
current_score = score
if current_score > best_score:
best_score = current_score
best_ans = assigns[:]
else:
# reject
assigns[idx] = old
T = T * a
print (*[v + 1 for v in best_ans])
这个大概可以拿 300+分,有点意外
这个操作因子,效率太低了,间接反映了比赛实际的数据相对较弱,容易得分。
具体更有效率的操作因子,可具体问问 AI 大模型。
写在最后

