
简介本资源为2022年电工杯数学建模竞赛B题获奖作品聚焦5G网络环境下应急物资配送的协同优化问题面向高校数学建模参赛者、运筹优化学习者及智能物流算法实践者。全文以PDF形式完整呈现赛题分析、四类递进式路径规划模型构建与求解全过程问题一采用模拟退火SA深度优先搜索DFS求解类TSP获582km最优里程问题二融合粒子群优化PSO与广度优先搜索BFS实现车机协同配送总耗时380分钟问题三、四基于K-means分区与遗传算法GA构建多约束VRP模型支持载重限制、双配送中心选址等复杂场景收敛稳定性强。资源含1个975KB PDF文件涵盖摘要、假设、符号说明、模型推导、算法流程图、结果验证及关键词结构严谨、公式与代码逻辑清晰。已有1753人学习下载提供可复现的完整建模思路、启发式算法组合策略及5G赋能应急物流的落地范例。1. 这不是TSP是带约束的多模态协同路径规划5G应急配送建模的本质跃迁2022年电工杯B题表面看是“14个点怎么走最短”但真正卡住90%参赛队的从来不是代码跑不跑得通而是没看清问题一的邻接矩阵里藏着6→4→6这种强制回环边——这意味着它根本不是标准TSP而是一个带硬约束的车辆单次满载配送问题。本文二等奖方案的破局点恰恰在于放弃“必须遍历所有点一次”的思维定式转而把整个配送过程解耦为“车辆主干网无人机毛细血管”的两级结构车辆只走高需求、长距离、可通行主干道实线无人机专攻短距、高时效、仅限空域的支线虚线。这种拆解直接绕开了传统VRP模型中“车辆-无人机耦合度高、状态空间爆炸”的死结。更关键的是它把5G网络的低时延特性转化成了算法优势——粒子群优化中个体位置更新不再依赖全局同步而是通过本地BFS快速生成可行子路径后用5G信令实时反馈路径可行性使PSO迭代效率提升3.7倍。对物流算法工程师而言这不是一道赛题而是真实应急场景下“如何让算法在30分钟内给出可执行方案”的工程范本对高校建模新手它展示了从邻接矩阵预处理9999填充、自环置0到启发式扰动设计SA温度衰减率α0.995的完整链路所有代码均基于OR-ToolsNumPy实现无黑盒依赖。2. 问题一非完全图TSP的SA-DFS混合求解——从邻接矩阵清洗到路径扰动策略2.1 邻接矩阵预处理为什么必须把不连通边设为9999而非∞原始附件1给出的14个地点邻接关系是稀疏非完全图直接使用np.inf会导致OR-Tools的RoutingModel在计算弧代价时触发浮点溢出错误。本文采用9999作为“不可达”标记其数值选择有严格工程依据最大单段距离为56km节点1→214节点全连通TSP理论最长路径不超过14×56≈784km9999远大于此值且在32位整数范围内安全。预处理代码需显式处理三类边界import numpy as np def preprocess_adj_matrix(raw_matrix): raw_matrix: 14x14原始邻接矩阵0表示无连接 n len(raw_matrix) # 步骤1自环置0节点到自身距离为0 adj np.array(raw_matrix, dtypefloat) np.fill_diagonal(adj, 0) # 步骤2不连通边置9999非零非原始值才替换 adj[adj 0] 9999 # 原始0表示无连接需替换 adj[adj 9999] 0 # 恢复已存在的0自环 # 步骤3对称化无向图假设 adj np.triu(adj) np.triu(adj, 1).T return adj.astype(int) # 示例附件1中节点0(即题目中第9点)到节点4(第5点)距离为26km # 预处理后adj[0][4] 26, adj[0][1] 9999不连通提示若跳过步骤2直接adj[adj0]9999会将所有自环也置为9999导致算法认为“车辆无法停靠自身”引发IndexError: Node index out of bounds。这是初学者最常踩的坑。2.2 SA-DFS混合框架如何用深度优先搜索生成初始路径并注入模拟退火扰动标准SA需要初始解而随机生成14!种排列穷举不可行。本文创新性地用DFS生成首条可行路径再以该路径为起点进行SA优化。DFS并非盲目遍历而是带贪心剪枝每次从当前节点选择未访问且距离最近的邻居。SA扰动策略设计为双层扰动——外层交换路径中两个随机位置的节点如[9,13,14,...]→[9,14,13,...]内层对扰动后路径执行DFS修复当交换导致断连时用DFS重连。温度衰减函数采用指数衰减T T0 * 0.995^k其中k为迭代次数T0100经实验验证可在200次内收敛。import random from typing import List, Tuple def dfs_path(adj: np.ndarray, start: int, visited: set) - List[int]: 从start出发DFS生成一条覆盖所有节点的路径允许重复访问 path [start] visited.add(start) while len(visited) len(adj): current path[-1] # 贪心选择找未访问且距离最小的邻居 candidates [(i, adj[current][i]) for i in range(len(adj)) if i not in visited and adj[current][i] 9999] if not candidates: # 无新节点可选回溯到上一个有候选的节点 path.pop() if not path: break continue next_node min(candidates, keylambda x: x[1])[0] path.append(next_node) visited.add(next_node) return path def sa_optimize(adj: np.ndarray, init_path: List[int], max_iter: int 200) - Tuple[List[int], float]: 模拟退火优化路径 current_path init_path.copy() current_cost calculate_path_cost(adj, current_path) best_path, best_cost current_path.copy(), current_cost T 100.0 for k in range(max_iter): # 双层扰动先交换再DFS修复 new_path current_path.copy() i, j random.sample(range(1, len(new_path)-1), 2) # 避开起点/终点 new_path[i], new_path[j] new_path[j], new_path[i] # DFS修复断连若交换后相邻节点不可达 repaired dfs_repair(adj, new_path) if repaired: new_cost calculate_path_cost(adj, repaired) delta new_cost - current_cost if delta 0 or random.random() np.exp(-delta / T): current_path, current_cost repaired, new_cost if new_cost best_cost: best_path, best_cost repaired.copy(), new_cost T * 0.995 # 温度衰减 return best_path, best_cost def calculate_path_cost(adj: np.ndarray, path: List[int]) - float: 计算路径总距离含返回起点 cost 0 for i in range(len(path)): from_node path[i] to_node path[(i1) % len(path)] # 循环到起点 cost adj[from_node][to_node] return cost表SA-DFS参数调优对照表基于附件1数据参数测试值200次迭代平均耗时最优解命中率关键现象初始温度T0501.2s68%降温过快易陷入局部最优初始温度T01003.79s92%收敛稳定与论文结果一致初始温度T02008.5s94%耗时翻倍收益边际递减衰减率 α0.994.1s89%后期扰动不足收敛慢衰减率 α0.9953.79s92%论文采用值平衡速度与精度衰减率 α0.99912.3s95%过度探索不实用2.3 结果验证为何穷举法139.816s vs SA-DFS仅3.79s关键在状态空间压缩论文提到穷举验证耗时139.816秒其本质是遍历所有哈密顿回路14节点完全图有13!/2≈3.1×10⁹种但非完全图实际可行路径仅147258种。SA-DFS的加速来自三重压缩空间压缩DFS生成初始路径时每步只选距离最近的2个邻居而非全部将分支因子从13降至2时间压缩SA接受劣解的概率exp(-ΔE/T)使算法在高温阶段快速穿越山谷避免在局部最优停留结构压缩利用问题特性节点6→4→6强制回环预处理时将该子图收缩为超节点路径长度从14维降至12维。验证代码需对比两种解的等价性论文给出路径9→13→14→10→6→4→6→5→3→2→5→7→1→11→12→8→9其等价形式包括起点平移如13→14→...→9→13和镜像反转9→8→12→...→9。验证脚本应自动检测这些变换def is_equivalent_path(path1: List[int], path2: List[int]) - bool: 判断两条路径是否为同一环路的不同表示 if len(path1) ! len(path2): return False # 生成path1的所有循环移位 n len(path1) rotations [path1[i:] path1[:i] for i in range(n)] # 生成path1的镜像反转后循环移位 reversed_path path1[::-1] rotations [reversed_path[i:] reversed_path[:i] for i in range(n)] return any(rot path2 for rot in rotations) # 验证论文路径与穷举最优路径是否等价 paper_path [8,12,13,9,5,4,6,0,10,11,7,8] # 论文原始索引0起始 exhaustive_best [0,10,11,7,8,12,13,9,5,4,6,0] # 穷举结果 print(is_equivalent_path(paper_path, exhaustive_best)) # True3. 问题二车辆-无人机协同的PSO-BFS分层建模——如何用广度优先搜索生成无人机子路径3.1 协同建模的核心矛盾车辆路径确定性与无人机路径动态性的解耦问题二引入无人机后状态空间呈指数级增长车辆路径有P种每条车辆路径对应Q种无人机分配方案总组合数P×Q远超计算能力。本文的突破在于分层决策第一层用PSO优化车辆主干路径决策变量为节点序列第二层对每条车辆路径用BFS独立生成各路段的无人机子路径。BFS在此处的作用不是找最短路而是枚举所有满足约束的无人机可达路径集合——例如车辆从9→8BFS需找出所有以9为起点、8为终点、且仅经过虚线边的路径如9→13→8、9→14→8再根据物资需求量选择最优者。from collections import deque def bfs_drone_paths(adj_vehicle: np.ndarray, adj_drone: np.ndarray, start: int, end: int, max_depth: int 5) - List[List[int]]: 在无人机专用邻接矩阵adj_drone上找start到end的所有路径长度≤max_depth adj_vehicle: 车辆可用边实线adj_drone: 无人机可用边虚线实线 paths [] queue deque([[start]]) while queue and len(paths) 100: # 限制枚举数量 path queue.popleft() node path[-1] if node end and len(path) 1: paths.append(path) continue if len(path) max_depth: continue # 只遍历无人机可达边adj_drone[node][i] 9999 for next_node in range(len(adj_drone)): if (adj_drone[node][next_node] 9999 and next_node not in path[1:-1]): # 避免中间节点重复 new_path path [next_node] queue.append(new_path) return paths # 示例车辆9→8无人机路径枚举 # adj_drone[9][13]15, adj_drone[13][8]12 → 路径[9,13,8]成本27 drone_paths bfs_drone_paths(adj_vehicle, adj_drone, start9, end8) print(f9→8无人机路径: {drone_paths}) # [[9,13,8], [9,14,8], ...]注意BFS中next_node not in path[1:-1]限制中间节点不重复但允许起点/终点重复如9→13→9→8非法但9→13→8→9在返回时合法。这是为后续遗传算法预留的扩展接口。3.2 PSO粒子编码为什么用节点序列而非二进制编码PSO标准编码为实数向量但路径规划需离散节点序列。本文采用排列编码Permutation Encoding每个粒子是14个节点的排列如[9,8,7,5,2,5,6,10,9]注意5和9重复出现表示返回。这种编码天然满足“每个节点至少被服务一次”的约束且BFS子路径可直接映射到粒子中相邻节点对。对比二进制编码每个节点用1bit表示是否被车辆服务排列编码的优势在于可行性保证无需解码校验所有粒子都是语法合法的路径邻域连续性交换两个节点位置产生的新粒子与原粒子在路径空间中距离相近BFS兼容性粒子中每对相邻节点(i,j)可直接调用bfs_drone_paths(i,j)生成无人机选项。PSO适应度函数设计为多目标加权fitness w1×(1/time) w2×(1/distance) w3×drone_coverage其中drone_coverage为被无人机服务的节点数。权重w10.5, w20.3, w30.2经网格搜索确定使时间指标主导优化方向应急场景首要目标是缩短总耗时。3.3 40次运行仅23次收敛局部最优的根因分析与改进策略论文坦承40次运行仅23次得到最优解57.5%根源在于PSO的早熟收敛Premature Convergence当粒子群过早聚集在某个局部最优区域速度更新公式v w*v c1*r1*(pbest-x) c2*r2*(gbest-x)中的gbest项会使所有粒子向同一方向坍缩。改进方案有三动态惯性权重w 0.9 - 0.5×(current_iter/max_iter)初期大权重探索后期小权重开发多样性维持当粒子群标准差阈值时随机重置10%粒子位置精英保留每代保留top-3粒子不参与更新防止最优解丢失。def pso_optimize(adj_vehicle: np.ndarray, adj_drone: np.ndarray, demands: List[float], max_iter: int 10000) - List[int]: n len(adj_vehicle) # 初始化粒子群每个粒子是随机排列含重复起点 particles [] for _ in range(50): # 50个粒子 base_perm list(range(n)) random.shuffle(base_perm) # 插入重复节点模拟返回如[9,8,7,5,2,5,6,10,9] path base_perm[:5] [base_perm[0]] base_perm[5:] [base_perm[0]] particles.append(path) # 初始化速度交换操作序列 velocities [[(random.randint(0,n-1), random.randint(0,n-1)) for _ in range(3)] for _ in range(50)] pbest, gbest particles.copy(), None pbest_fitness [-float(inf)] * 50 gbest_fitness -float(inf) w, c1, c2 0.9, 1.5, 1.5 for t in range(max_iter): # 动态调整w w 0.9 - 0.5 * (t / max_iter) for i in range(50): # 计算当前粒子适应度 fitness evaluate_pso_particle(particles[i], adj_vehicle, adj_drone, demands) if fitness pbest_fitness[i]: pbest[i], pbest_fitness[i] particles[i].copy(), fitness if fitness gbest_fitness: gbest, gbest_fitness particles[i].copy(), fitness # 更新速度交换操作 r1, r2 random.random(), random.random() for swap in velocities[i]: if random.random() w: # 惯性项保持原交换 pass if random.random() c1 * r1: # pbest项向个体最优交换靠拢 idx1, idx2 random.sample(range(len(pbest[i])), 2) velocities[i].append((idx1, idx2)) if random.random() c2 * r2: # gbest项向全局最优交换靠拢 idx1, idx2 random.sample(range(len(gbest)), 2) velocities[i].append((idx1, idx2)) # 执行速度应用交换操作 for idx1, idx2 in velocities[i]: if idx1 len(particles[i]) and idx2 len(particles[i]): particles[i][idx1], particles[i][idx2] \ particles[i][idx2], particles[i][idx1] # 多样性维持当粒子相似度0.8重置10%粒子 if t % 100 0: diversity calculate_diversity(particles) if diversity 0.2: for i in range(5): particles[i] generate_random_path(n) return gbest def evaluate_pso_particle(path: List[int], adj_v: np.ndarray, adj_d: np.ndarray, demands: List[float]) - float: 计算粒子适应度时间、距离、无人机覆盖率加权 total_time 0 total_dist 0 drone_nodes set() for i in range(len(path)-1): v_from, v_to path[i], path[i1] # 车辆行驶时间 距离 / 60km/h dist_v adj_v[v_from][v_to] total_dist dist_v total_time dist_v / 60.0 # 无人机路径BFS找最优子路径 drone_path bfs_drone_paths(adj_v, adj_d, v_from, v_to)[0] if drone_path: drone_nodes.update(drone_path[1:-1]) # 添加中间服务节点 coverage len(drone_nodes) / len(demands) return 0.5*(1/(total_time1)) 0.3*(1/(total_dist1)) 0.2*coverage4. 问题三与四K-means聚类驱动的VRP遗传算法——从硬聚类到软分区的演进4.1 为什么必须用K-means载重约束下的地理分区本质问题三中车辆载重500kg 总需求782kg强制车辆至少返回一次这使问题从单路径TSP升维为多路径VRP。若直接对14节点做VRP需决策“哪些节点归第一趟哪些归第二趟”组合数C(14,7)≈3432种但其中大量分区地理上不连续如{1,3,5,7,9,11,13}分散全图。K-means的价值在于将组合优化转化为连续空间聚类用节点坐标附件2提供经纬度作为特征K2聚类天然产生地理邻近的两组大幅减少无效分区。论文中聚类结果[8,7,5,2,1,11,12,13]与[3,4,6,10,14]正是地理上东西分部的体现。from sklearn.cluster import KMeans import numpy as np def kmeans_partition(coords: np.ndarray, k: int 2) - np.ndarray: coords: n×2数组每行是节点经纬度 返回: n维数组值为0或1表示所属簇 kmeans KMeans(n_clustersk, random_state42, n_init10) labels kmeans.fit_predict(coords) return labels # 示例附件2中14个节点坐标简化 coords np.array([ [116.3, 39.9], # 节点0题目第9点 [116.4, 39.8], # 节点1第10点 # ... 其他12个节点 ]) labels kmeans_partition(coords, k2) cluster0 np.where(labels 0)[0] # 簇0节点索引 cluster1 np.where(labels 1)[0] # 簇1节点索引 print(f簇0节点: {cluster0}, 簇1节点: {cluster1})提示K-means对初始中心敏感n_init10确保运行10次取最优。若坐标未标准化经纬度量纲不同需先StandardScaler否则经度116主导聚类。4.2 遗传算法编码0分隔符编码如何天然满足载重约束VRP遗传算法难点在于编码需同时满足①每个节点恰好被服务一次②每条子路径载重≤500kg③子路径以配送中心为起点/终点。本文采用0分隔符编码Zero-Delimited Encoding染色体为节点排列若干0如[6,8,7,9,0,6,5,4,1,2,3]表示第一趟6→8→7→9→6第二趟6→5→4→1→2→3→6。0的位置决定分组节点值决定顺序。这种编码的妙处在于约束嵌入解码时按0切片对每段累加需求量若超500kg则立即截断并插入0使所有生成个体天然合法变异安全交换两个非0元素如[6,8,7,9,0,6,5,4,1,2,3]→[6,7,8,9,0,6,5,4,1,2,3]只要原路径合法新路径载重不变交叉兼容虽论文弃用交叉但若启用可设计“顺序交叉OX”保持相对顺序。def decode_chromosome(chrom: List[int], depot: int, demands: List[float], capacity: int 500) - List[List[int]]: 将染色体解码为多条路径 chrom: 如[6,8,7,9,0,6,5,4,1,2,3] 返回: [[6,8,7,9,6], [6,5,4,1,2,3,6]] paths [] current_path [depot] current_load 0 for gene in chrom: if gene 0: # 遇到0结束当前路径添加返回仓库 if len(current_path) 1: current_path.append(depot) paths.append(current_path) current_path [depot] current_load 0 else: # 添加节点检查载重 if current_load demands[gene] capacity: current_path.append(gene) current_load demands[gene] else: # 超载结束当前路径开启新路径 current_path.append(depot) paths.append(current_path) current_path [depot, gene] current_load demands[gene] # 处理最后一段 if len(current_path) 1: current_path.append(depot) paths.append(current_path) return paths # 示例染色体解码 chrom [6,8,7,9,0,6,5,4,1,2,3] demands [0, 120, 85, 92, 78, 110, 65, 95, 88, 72, 105, 60, 80, 90] # 节点0-13需求 paths decode_chromosome(chrom, depot9, demandsdemands) print(f解码路径: {paths}) # 输出: [[9,6,8,7,9], [9,6,5,4,1,2,3,9]]4.3 问题四的升级双配送中心选址与四分区VRP的嵌套优化问题四规模升至30节点且需决策两个配送中心位置。若暴力枚举所有节点对作为中心C(30,2)435种每种再K-means聚类VRP优化计算量不可承受。本文采用嵌套优化外层用K-means找2个质心即配送中心候选内层对每个质心聚类结果做VRP。K-means质心即地理中心故配送中心必为某节点只需评估30个节点作为中心的聚类质量。def two_depot_optimization(coords: np.ndarray, demands: List[float], n_nodes: int 30) - Tuple[List[int], List[List[int]]]: 返回: 最优配送中心列表, 对应的分区VRP路径 best_score float(inf) best_depots None best_paths None # 枚举所有节点对作为配送中心 for i in range(n_nodes): for j in range(i1, n_nodes): depots [i, j] # 用这两个节点初始化K-means init_centers np.array([coords[i], coords[j]]) kmeans KMeans(n_clusters2, initinit_centers, n_init1, max_iter300) labels kmeans.fit_predict(coords) # 按标签分组节点 cluster0_nodes np.where(labels 0)[0] cluster1_nodes np.where(labels 1)[0] # 对每个簇独立运行VRP paths0 vrp_genetic(cluster0_nodes, depoti, demandsdemands) paths1 vrp_genetic(cluster1_nodes, depotj, demandsdemands) # 计算总成本时间距离 score calculate_total_cost(paths0, paths1, coords, demands) if score best_score: best_score score best_depots depots best_paths [paths0, paths1] return best_depots, best_paths def vrp_genetic(nodes: np.ndarray, depot: int, demands: List[float], pop_size: int 10000) - List[List[int]]: 对单簇节点运行VRP遗传算法 # 初始化种群随机排列插入0 population [] for _ in range(pop_size): perm nodes.tolist() random.shuffle(perm) # 插入0分隔符约2-3个0 for _ in range(random.randint(2,3)): pos random.randint(1, len(perm)-1) perm.insert(pos, 0) population.append(perm) # 遗传算法主循环选择、变异、评估 # ...同问题三略 return best_paths5. 工程落地关键从数学模型到可部署代码的四大转换技巧5.1 OR-Tools与自研算法的混合调度——何时用库何时手写本文附录1-4显示问题一用OR-Tools的RoutingModel问题二三四用自研PSO/遗传算法。这不是技术偏好而是工程权衡OR-Tools的PATH_CHEAPEST_ARC策略在小规模n≤20TSP上极快但其约束系统难以表达“无人机仅走虚线”这类异构边约束。因此混合策略为阶段1路径骨架用OR-Tools求解车辆主干路径实线图因其求解器针对稀疏图优化阶段2子路径填充对OR-Tools输出的每条车辆边(i,j)调用自研BFS在虚线图上生成无人机路径阶段3多目标优化当需权衡时间/距离/覆盖率时用自研PSO/遗传算法因其适应度函数可任意定义。# 混合调度伪代码 def hybrid_solver(): # 阶段1OR-Tools求车辆路径 vehicle_path ortools_tsp_solver(adj_vehicle) # 阶段2BFS生成无人机子路径 drone_subpaths {} for i in range(len(vehicle_path)-1): v_from, v_to vehicle_path[i], vehicle_path[i1] drone_subpaths[(v_from, v_to)] bfs_drone_paths( adj_vehicle, adj_drone, v_from, v_to) # 阶段3PSO优化最终方案调整vehicle_path并重算drone_subpaths final_solution pso_optimize_with_drone( vehicle_path, drone_subpaths, demands) return final_solution5.2 邻接矩阵的双重构建车辆图与无人机图的物理意义分离附件2中实线/虚线区分必须构建两个邻接矩阵adj_vehicle实线边权重距离虚线边权重9999车辆不可走adj_drone实线虚线边权重距离无边则9999。关键陷阱不能简单将虚线距离设为0因为BFS中0权重边会被优先选择导致算法误判“无人机瞬移”。正确做法是保持物理距离让适应度函数自然惩罚长距无人机飞行。# 错误示范虚线距离设0 adj_drone_wrong np.where(is_drone_edge, 0, 9999) # 正确做法虚线距离实际距离如9→1315km adj_drone_correct np.where(is_drone_edge, actual_distance, 9999)5.3 时间复杂度控制从O(n!)到O(n²logn)的降维实践所有算法的时间瓶颈在BFS/DFS的路径枚举。本文通过三级剪枝实现降维深度剪枝BFS设置max_depth5因无人机航程有限5G信号覆盖半径制约代价剪枝DFS中若当前路径成本已知最优成本×1.2立即回溯相似性剪枝PSO中若两粒子路径汉明距离2合并为同一粒子。def dfs_with_pruning(adj: np.ndarray, path: List[int], visited: set, best_cost: float, alpha: float 1.2) - List[int]: 带代价剪枝的DFS if len(path) 1: current_cost calculate_path_cost(adj, path) if current_cost best_cost * alpha: return None # 剪枝 # ... DFS主体逻辑 return path5.4 结果可视化用NetworkX绘制车辆-无人机协同路径图可部署代码必须包含结果可视化否则无法向决策者解释方案。以下代码生成专业级路径图车辆路径粗线红色无人机路径细线蓝色节点大小表示物资需求量import networkx as nx import matplotlib.pyplot as plt def plot_solution(vehicle_path: List[int], drone_paths: Dict[Tuple[int,int], List[int]], coords: np.ndarray, demands: List[float]): G nx.Graph() pos {i: (coords[i][0], coords[i][1]) for i in range(len(coords))} # 添加所有节点 for i in range(len(coords)): G.add_node(i, demanddemands[i]) # 绘制车辆路径红色粗线 for i in range(len(vehicle_path)-1): G.add p a hrefhttps://download.csdn.net/download/maligebilaowang/85951461 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p