如何用C#自动生成含多停站与多轮胎配方的最优赛车策略
问题描述
我正在编写C#脚本,计算40圈赛事的最短完成时间,需要结合轮胎配方(软胎、中性胎)与进站策略(进站间隔圈数,称为segment)。当前脚本暴力枚举1次进站的所有可行组合(单段圈数不超过25,共20种轮胎与段数组合),但扩展至2次进站(3段)策略时,手动实现方式不再可行。请问是否存在更高效的方法来生成并遍历所有可行赛车策略?能否自动生成策略而非手动列举?
以下是当前脚本(因用于数学论文,使用decimal而非double):
using System; using System.Collections.Generic; using System.Linq; public class Program { public static void Main() { List<decimal> findMin = new List<decimal>(); //Add all possible values to the list for(int i = 25; i >= 20; i--) { findMin.Add(LowestCombo(i, 40 - i)); } Console.WriteLine(" "); //Print lowest number in the list Console.WriteLine($"The lowest possible time achievable is {findMin.Min()}"); } public static decimal LowestCombo (int a, int b){ List<decimal> sumBoth = new List<decimal>(); string[] identifier = {"all soft tyres", "all medium tyres", "soft, then medium", "medium, then soft"}; //Add all possible combinations of the two to the list sumBoth.Add(SoftSummation(a) + SoftSummation(b)); sumBoth.Add(MedSummation(a) + MedSummation(b)); sumBoth.Add(SoftSummation(a) + MedSummation(b)); sumBoth.Add(MedSummation(a) + SoftSummation(b)); //Print the combination of laps and tyres as well as the time, then return the lowest time Console.WriteLine($"The lowest possible time achievable with {a} initial laps and {b} final laps for a 1 pit stop race is {sumBoth.Min()}, {identifier[sumBoth.IndexOf(sumBoth.Min())]}."); return sumBoth.Min(); } public static decimal SoftSummation(int a) { decimal sum = 0; for(int x = 1; x <= a; x++) { //Tyre degradation function for the soft compound (Math.Pow can't take decimal??) sum += (0.1262m * (x * x * x * x)) - (4.476m * (x * x * x)) + (56.37m * (x * x)) - (152.9m * x) + (1.427m * (100000m)); } return sum; } public static decimal MedSummation(int a) { decimal sum = 0; for(int x = 1; x <= a; x++) { //Tyre degradation function for the medium compound sum += (0.8406m * (x * x)) + (44.77m * x) + (1.434m * 100000m); } return sum; } }
解决方案
1. 抽象轮胎逻辑并预计算单圈时间
先把轮胎类型抽象成枚举,同时预计算每圈的时间(避免重复求和),后续扩展轮胎类型只需添加新的预计算逻辑:
public enum TyreType { Soft, Medium } // 预存储每种轮胎的单圈时间(1到40圈) private static readonly Dictionary<TyreType, List<decimal>> _tyreLapTimes = new Dictionary<TyreType, List<decimal>>(); // 初始化预计算数据 static Program() { // 计算软胎每圈时间 var softTimes = new List<decimal>(); for (int lap = 1; lap <= 40; lap++) { decimal time = (0.1262m * (decimal)Math.Pow(lap, 4)) - (4.476m * (decimal)Math.Pow(lap, 3)) + (56.37m * lap * lap) - (152.9m * lap) + (1.427m * 100000m); softTimes.Add(time); } _tyreLapTimes[TyreType.Soft] = softTimes; // 计算中性胎每圈时间 var medTimes = new List<decimal>(); for (int lap = 1; lap <= 40; lap++) { decimal time = (0.8406m * lap * lap) + (44.77m * lap) + (1.434m * 100000m); medTimes.Add(time); } _tyreLapTimes[TyreType.Medium] = medTimes; } // 计算某段连续圈数的总时间(轮胎从第1圈开始磨损) public static decimal CalculateSegmentTime(TyreType tyre, int segmentLaps) { decimal total = 0; for (int i = 0; i < segmentLaps; i++) { total += _tyreLapTimes[tyre][i]; } return total; }
2. 自动生成所有合法分段组合
通过递归生成任意进站次数对应的分段组合,自动满足「单段圈数≤25、总圈数=40、每段至少1圈」的规则:
// 生成指定进站次数的所有合法分段 public static List<List<int>> GenerateSegments(int totalLaps, int pitStopCount, int maxLapsPerSegment) { var result = new List<List<int>>(); int segmentCount = pitStopCount + 1; GenerateSegmentsRecursive(totalLaps, segmentCount, maxLapsPerSegment, new List<int>(), result); return result; } private static void GenerateSegmentsRecursive(int remainingLaps, int remainingSegments, int maxPerSegment, List<int> current, List<List<int>> result) { if (remainingSegments == 1) { if (remainingLaps >= 1 && remainingLaps <= maxPerSegment) { var final = new List<int>(current); final.Add(remainingLaps); result.Add(final); } return; } // 每段至少1圈,且剩余段数需保留至少1圈的余量 int end = Math.Min(maxPerSegment, remainingLaps - (remainingSegments - 1)); for (int i = 1; i <= end; i++) { current.Add(i); GenerateSegmentsRecursive(remainingLaps - i, remainingSegments - 1, maxPerSegment, current, result); current.RemoveAt(current.Count - 1); } }
3. 生成所有轮胎组合并计算最优解
通过笛卡尔积生成所有轮胎搭配,遍历所有分段+轮胎的组合,计算总时间并记录最小值:
public static void FindOptimalStrategy(int totalLaps, int pitStopCount, int maxLapsPerSegment) { int segmentCount = pitStopCount + 1; var segments = GenerateSegments(totalLaps, pitStopCount, maxLapsPerSegment); var tyreTypes = Enum.GetValues(typeof(TyreType)).Cast<TyreType>().ToList(); decimal minTotalTime = decimal.MaxValue; List<int> bestSegments = null; List<TyreType> bestTyreCombo = null; foreach (var segment in segments) { // 生成该分段对应的所有轮胎组合 var tyreCombos = GenerateTyreCombinations(tyreTypes, segmentCount); foreach (var tyres in tyreCombos) { decimal totalTime = 0; for (int i = 0; i < segmentCount; i++) { totalTime += CalculateSegmentTime(tyres[i], segment[i]); } // 更新最优策略 if (totalTime < minTotalTime) { minTotalTime = totalTime; bestSegments = segment; bestTyreCombo = tyres; } } } // 输出结果 Console.WriteLine($"最优策略({pitStopCount}次进站):"); Console.WriteLine($"总时间:{minTotalTime}"); Console.WriteLine("分段详情:"); for (int i = 0; i < bestSegments.Count; i++) { Console.WriteLine($"第{i+1}段:{bestSegments[i]}圈,{bestTyreCombo[i]}胎"); } } // 生成指定长度的轮胎组合(笛卡尔积) public static List<List<TyreType>> GenerateTyreCombinations(List<TyreType> tyreTypes, int length) { if (length == 0) { return new List<List<TyreType>> { new List<TyreType>() }; } var result = new List<List<TyreType>>(); var subCombos = GenerateTyreCombinations(tyreTypes, length - 1); foreach (var combo in subCombos) { foreach (var tyre in tyreTypes) { var newCombo = new List<TyreType>(combo); newCombo.Add(tyre); result.Add(newCombo); } } return result; }
4. 调用示例
在Main方法中直接调用即可计算任意进站次数的最优策略:
public static void Main() { // 计算2次进站(3段)的最优策略 FindOptimalStrategy(40, 2, 25); }
核心优势
- 自动适配任意进站次数:无需手动修改代码,传入进站次数即可生成对应分段
- 预计算提升效率:避免重复计算单圈时间,减少冗余运算
- 全组合覆盖:自动生成所有合法的分段和轮胎搭配,不会遗漏最优解
内容的提问来源于stack exchange,提问作者Kee
相关产品推荐
相关产品推荐

