Qwen 3.5plus一步做对的欧拉计划701题

我上传了从欧拉计划翻译网站https://pe-cn.github.io/701/截取的中文图像

编写解决附件中题目的python程序

这是一个经典的概率期望问题,可以通过动态规划(Dynamic Programming)结合轮廓线(Broken Profile / Plug DP)来解决。由于网格较小 (7×77 \times 77×7),但状态空间较大,我们需要精心设计状态。

算法思路

  1. 目标 : 计算所有 2492^{49}249 种染色方案中,最大连通区域面积的总和,然后除以 2492^{49}249 得到期望。
  2. 状态定义 : 我们逐格处理网格。对于每个格子 (r,c)(r, c)(r,c),我们需要记录当前"轮廓线"上的连通性状态。
    • mask: 一个元组,表示当前轮廓线上每个位置所属的连通块 ID。
    • sizes: 一个元组,记录每个连通块 ID 当前的面积(黑色格子数)。
    • 为了合并相同状态,我们需要对 mask 进行最小表示法标准化(例如,第一个出现的连通块标记为 1,第二个为 2...)。
  3. DP 值 : 对于每个状态 (mask, sizes),我们需要记录一个直方图(字典),hist[k] = count,表示到达该状态且已封闭连通块的最大面积 为 kkk 的路径数量。
    • 为什么需要记录 max_closed?因为当我们在某处放置白色格子时,可能会封闭一个连通块。这个封闭块的大小可能成为最终的最大连通块面积。
  4. 转移 :
    • 染白色 : 当前格子变为白色。检查上方格子所属的连通块是否被封闭(即在轮廓线其他位置不再出现)。如果封闭,更新 max_closed
    • 染黑色 : 当前格子变为黑色。尝试与上方 (up) 和左方 (left) 的连通块合并。
      • 如果 upleft 都存在且不同,合并它们,新面积 = size[up] + size[left] + 1
      • 如果只有一个存在,继承它,面积 +1。
      • 如果都不存在,创建新连通块,面积 = 1。
      • 注意检查合并后的面积(虽然题目没限制最大面积,但我们需要记录它)。
  5. 结果计算 : 处理完所有格子后,遍历所有最终状态。对于每个状态,计算剩余活跃连通块的最大面积 max_active。最终该状态对总和的贡献是 ∑k(count×max⁡(k,max_active))\sum_{k} (\text{count} \times \max(k, \text{max\_active}))∑k(count×max(k,max_active))。

Python 代码实现

python 复制代码
import sys

# 增加递归深度,虽然主要使用迭代
sys.setrecursionlimit(5000)

def solve(W, H):
    # 状态: (mask_tuple, sizes_tuple) -> { max_closed_area: count }
    # mask_tuple: 长度为 W 的元组,表示轮廓线上每列的连通块 ID (0 表示白色/无)
    # sizes_tuple: 长度为 K 的元组,sizes[i] 表示 ID 为 i 的连通块当前的面积
    # 使用字典存储状态及其对应的 max_closed 分布
    
    # 初始状态: 全 0
    initial_mask = tuple([0] * W)
    initial_sizes = tuple([0]) # ID 0 的大小为 0,ID 从 1 开始,动态扩展
    
    # current_states 存储所有可能的状态
    # key: (mask, sizes), value: dict {max_closed: count}
    current_states = {
        (initial_mask, initial_sizes): {0: 1}
    }
    
    for r in range(H):
        for c in range(W):
            next_states = {}
            
            for (mask, sizes), hist in current_states.items():
                # 当前格子 (r, c)
                # 上方格子对应的 ID (在 mask 的索引 c 处)
                up_id = mask[c]
                # 左方格子对应的 ID (在 mask 的索引 c-1 处)
                left_id = mask[c-1] if c > 0 else 0
                
                # --- 选项 1: 染白色 ---
                # 染白色意味着当前格子不属于任何黑色连通块
                # mask[c] 变为 0
                new_mask_list = list(mask)
                new_mask_list[c] = 0
                
                # 检查上方连通块是否封闭
                closed_size = 0
                if up_id != 0:
                    # 如果 up_id 不再出现在 new_mask_list 中,说明该连通块封闭
                    if up_id not in new_mask_list:
                        # 获取该 ID 的大小
                        if up_id < len(sizes):
                            closed_size = sizes[up_id]
                        else:
                            closed_size = 0 # 理论上不会发生
                
                temp_mask = tuple(new_mask_list)
                temp_sizes = sizes 
                
                # Normalize
                norm_mask, norm_sizes = normalize(temp_mask, temp_sizes)
                
                key = (norm_mask, norm_sizes)
                if key not in next_states:
                    next_states[key] = {}
                
                target_hist = next_states[key]
                for old_mc, count in hist.items():
                    new_mc = old_mc if old_mc > closed_size else closed_size
                    target_hist[new_mc] = target_hist.get(new_mc, 0) + count
                
                # --- 选项 2: 染黑色 ---
                new_mask_list_black = list(mask)
                # sizes 需要转为列表以便修改
                temp_sizes_list = list(sizes)
                
                if up_id == 0 and left_id == 0:
                    # 新连通块,分配新 ID
                    # 使用当前未使用的最小正整数,或者简单地用 len(sizes) (如果 sizes 紧凑)
                    # 由于 normalize 会重排,这里可以用任意未冲突 ID。
                    # 为了简单,用 max(mask) + 1,如果 mask 全 0 则用 1
                    # 但要注意 sizes 的索引。
                    # 我们直接用 len(temp_sizes_list) 作为新 ID,这样保证不越界且唯一
                    new_id = len(temp_sizes_list)
                    new_mask_list_black[c] = new_id
                    temp_sizes_list.append(1) # 新块大小为 1
                    
                elif up_id != 0 and left_id == 0:
                    # 继承上方
                    new_mask_list_black[c] = up_id
                    if up_id >= len(temp_sizes_list):
                        temp_sizes_list.extend([0] * (up_id - len(temp_sizes_list) + 1))
                    temp_sizes_list[up_id] += 1
                    
                elif up_id == 0 and left_id != 0:
                    # 继承左方
                    new_mask_list_black[c] = left_id
                    if left_id >= len(temp_sizes_list):
                        temp_sizes_list.extend([0] * (left_id - len(temp_sizes_list) + 1))
                    temp_sizes_list[left_id] += 1
                    
                else: # up_id != 0 and left_id != 0
                    if up_id == left_id:
                        # 同一个块
                        new_mask_list_black[c] = up_id
                        if up_id >= len(temp_sizes_list):
                            temp_sizes_list.extend([0] * (up_id - len(temp_sizes_list) + 1))
                        temp_sizes_list[up_id] += 1
                    else:
                        # 合并
                        main_id = min(up_id, left_id)
                        other_id = max(up_id, left_id)
                        
                        new_mask_list_black[c] = main_id
                        
                        # 确保列表足够长
                        if main_id >= len(temp_sizes_list):
                            temp_sizes_list.extend([0] * (main_id - len(temp_sizes_list) + 1))
                        if other_id >= len(temp_sizes_list):
                            temp_sizes_list.extend([0] * (other_id - len(temp_sizes_list) + 1))
                            
                        temp_sizes_list[main_id] += temp_sizes_list[other_id] + 1 
                        
                        # 将 mask 中所有的 other_id 替换为 main_id
                        for i in range(W):
                            if new_mask_list_black[i] == other_id:
                                new_mask_list_black[i] = main_id

                temp_mask_black = tuple(new_mask_list_black)
                temp_sizes_black = tuple(temp_sizes_list)
                
                norm_mask_b, norm_sizes_b = normalize(temp_mask_black, temp_sizes_black)
                key_b = (norm_mask_b, norm_sizes_b)
                
                if key_b not in next_states:
                    next_states[key_b] = {}
                
                target_hist_b = next_states[key_b]
                for old_mc, count in hist.items():
                    new_mc = old_mc 
                    target_hist_b[new_mc] = target_hist_b.get(new_mc, 0) + count
            
            current_states = next_states
            # 可选:打印进度
            # if r == H - 1 and c % 2 == 0:
            #    print(f"Processed ({r}, {c}), states: {len(current_states)}")
            
    # 计算最终结果
    total_sum_max_area = 0
    
    for (mask, sizes), hist in current_states.items():
        # 此时所有格子处理完毕。
        # mask 中可能还有活跃的连通块 (最后一行)。
        unique_ids = set(mask) - {0}
        max_active = 0
        for uid in unique_ids:
            if uid < len(sizes):
                if sizes[uid] > max_active:
                    max_active = sizes[uid]
        
        for mc, count in hist.items():
            final_max = mc if mc > max_active else max_active
            total_sum_max_area += count * final_max
            
    total_configs = 2 ** (W * H)
    expected_value = total_sum_max_area / total_configs
    return expected_value

def normalize(mask, sizes):
    """
    将 mask 转换为最小表示法,并相应重排 sizes。
    """
    mapping = {}
    next_id = 1
    new_mask_list = []
    
    for x in mask:
        if x == 0:
            new_mask_list.append(0)
        else:
            if x not in mapping:
                mapping[x] = next_id
                next_id += 1
            new_mask_list.append(mapping[x])
            
    new_mask = tuple(new_mask_list)
    
    # 重排 sizes
    # ID 0 的大小恒为 0 (虽然 sizes[0] 可能无意义,我们保持它为 0)
    # 新 ID k 对应旧 ID mapping^{-1}(k)
    
    inv_mapping = {v: k for k, v in mapping.items()}
    
    # 新 sizes 长度为 next_id (因为 ID 从 1 到 next_id-1, 加上 0)
    new_sizes_list = [0] * next_id
    
    for new_id, old_id in inv_mapping.items():
        if old_id < len(sizes):
            new_sizes_list[new_id] = sizes[old_id]
        else:
            new_sizes_list[new_id] = 0
            
    new_sizes = tuple(new_sizes_list)
    
    return new_mask, new_sizes

if __name__ == '__main__':
    print("Calculating E(2, 2)...")
    e_2_2 = solve(2, 2)
    print(f"E(2, 2) = {e_2_2}") 
    
    print("Calculating E(4, 4)...")
    e_4_4 = solve(4, 4)
    print(f"E(4, 4) = {e_4_4:.8f}") 
    
    print("Calculating E(7, 7)...")
    e_7_7 = solve(7, 7)
    print(f"E(7, 7) = {e_7_7:.8f}")
相关推荐
Book思议-1 小时前
【数据结构实战】链表找环入口的经典问题:快慢指针法
c语言·数据结构·算法·链表
tankeven2 小时前
HJ135 计树
c++·算法
㓗冽2 小时前
时间转换-进阶题12
c++·算法
炽烈小老头2 小时前
【 每天学习一点算法 2026/03/19】子集
学习·算法
我能坚持多久2 小时前
【初阶数据结构11】——链式二叉树知识补充
数据结构·算法
liuyao_xianhui2 小时前
优选算法_两数之和_位运算_C++
java·开发语言·数据结构·c++·算法·链表·动态规划
靠沿2 小时前
【优选算法】专题十六——BFS解决最短路径问题
redis·算法·宽度优先
博风2 小时前
算法:双指针解:盛最多水的容器
算法·leetcode
夏乌_Wx2 小时前
图 | 3道LeetCode常见笔试、面试题汇总
算法