Capacitated Facility Location Problem-蚁群算法-遗传算法-js实现

问题描述:

  • Suppose there are n facilities and m customers. We wish
    to choose:
    • which of the n facilities to open
    • the assignment of customers to facilities
  • The objective is to minimize the sum of the opening cost
    and the assignment cost.
  • The total demand assigned to a facility must not exceed
    its capacity.

report

在本次实践中,我分别采用了蚁群算法和遗传算法来对问题进行求解。

蚁群算法是一种用来寻找优化路径的概率型算法。它由Marco Dorigo于1992年在他的博士论文中提出,其灵感来源于蚂蚁在寻找食物过程中发现路径的行为。

遗传算法(Genetic Algorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法

Capacitated Facility Location Problem-蚁群算法-遗传算法-js实现

代码


// 遗传算法

/** 染色体数量 */
let chromosomeNum = 100

/** 适应度矩阵(下标:染色体编号、值:该染色体的适应度) */
let adaptability = []
/** 自然选择的概率矩阵(下标:染色体编号、值:该染色体被选择的概率) */
let selectionProbability = []

/** 染色体复制的比例(每代中保留适应度较高的染色体直接成为下一代) */
let cp = 0.2
/** 参与交叉变异的染色体数量 */
let crossoverMutationNum = chromosomeNum * (1-cp)

/** 任务处理时间结果集([迭代次数][染色体编号]) */
// let resultData = []

/**
 * 初始化遗传算法
 * 初始化 顾客集合、处理设施集合
 * @param _facilities 设施参数
 * @param _demands 顾客的要求
 * @param _assignmentCost 顾客到指定设施的cost
 */

function startGa(_facilities, _demands, _assignmentCost) {
    facilities = _facilities.reduce((all, now) => all.concat(Number(now.split(' ').filter(n=>n)[0])), [])

    openCost = _facilities.reduce((all, now) => all.concat(Number(now.split(' ').filter(n=>n)[1])), [])

    customers= _demands.reduce((all, now) => all.concat(now.split(' ').filter(n =>n).map(n =>Number(n))), [])

    let tp =[]
    /* _assignmentCost.forEach(_assign => {
        assignment.push(_assign.split(' ').filter(n => n).map(n => Number(n)))
    }) */
    
    let nums = _assignmentCost.reduce((all, now) => all.concat(now.split(' ').filter(n =>n).map(n =>Number(n))), [])
    while (nums.length) {
        console.log(nums)
        tp.push(nums.splice(0, customerNum))
    }
    debugger
    for (let i = 0; i < customerNum; i++) {
        for (let j = 0; j < facilityNum; j++) {
            if (!assignment[i]) {
                assignment[i] = []
            }
            assignment[i][j] = tp[j][i]
        }
    }


}

// startGa(lines.slice(1, facilityNum+1), lines.slice(facilityNum+1, facilityNum+1+customerNum/10), lines.slice(facilityNum+1+customerNum/10))

/**
 * 遗传算法
 */
function ga() {
    // 迭代搜索
    gaSearch(iteratorNum, chromosomeNum)
}

/**
 * 计算 染色体适应度
 * @param chromosomeMatrix
 */
function calAdaptability(chromosomeMatrix) {
    // 计算每条染色体的cost
    adaptability = calCost_oneIt(chromosomeMatrix).map(n => 1/n)
}

/**
 * 计算自然选择概率
 * @param adaptability
 */
function calSelectionProbability(adaptability) {
    selectionProbability = []

    // 计算适应度总和
    let sumAdaptability = 0
    for (let i=0; i<chromosomeNum; i++) {
        sumAdaptability += adaptability[i]
    }

    // 计算每条染色体的选择概率
    for (let i=0; i<chromosomeNum; i++) {
        selectionProbability.push(adaptability[i] / sumAdaptability)
    }
}

/**
 * 迭代搜索
 * @param iteratorNum 迭代次数
 * @param chromosomeNum 染色体数量
 */
function gaSearch(iteratorNum, chromosomeNum) {
    // 初始化第一代染色体
    let chromosomeMatrix = createGeneration()

    // 迭代繁衍
    for (let itIndex=1; itIndex<iteratorNum; itIndex++) {
        // 计算上一代各条染色体的适应度
        calAdaptability(chromosomeMatrix)

        // 计算自然选择概率
        calSelectionProbability(adaptability)

        // 生成新一代染色体
        chromosomeMatrix = createGeneration(chromosomeMatrix)
    }
}


/**
 * 交叉生成{crossoverMutationNum}条染色体
 * @param chromosomeMatrix 上一代染色体矩阵
 */
function cross(chromosomeMatrix) {
    let newChromosomeMatrix = []
    for (let chromosomeIndex=0; chromosomeIndex<crossoverMutationNum; chromosomeIndex++) {

        // 采用轮盘赌选择父母染色体
        let chromosomeDad = chromosomeMatrix[RWS(selectionProbability)].slice(0)

        let chromosomeMom = chromosomeMatrix[RWS(selectionProbability)].slice(0)
        // 交叉
        let crossIndex = random(0, customerNum-1)
        chromosomeDad.splice(crossIndex)
        chromosomeDad = chromosomeDad.concat(chromosomeMom.slice(crossIndex))

        while (!checkIfLegal(chromosomeDad)) {
            console.log('cross')
            chromosomeDad = chromosomeMatrix[RWS(selectionProbability)].slice(0)

            chromosomeMom = chromosomeMatrix[RWS(selectionProbability)].slice(0)

            crossIndex = random(0, customerNum-1)
            chromosomeDad.splice(crossIndex)
            chromosomeDad = chromosomeDad.concat(chromosomeMom.slice(crossIndex))
        }
        newChromosomeMatrix.push(chromosomeDad)
    }
    return newChromosomeMatrix
}

/*
 *看一条染色体是否是可行解
 *@param chromosome 染色体
 */

function checkIfLegal(chromosome) {

    let usedCapacity= {}
    for (let i = 0; i < chromosome.length; i++) {
        if (!usedCapacity[chromosome[i]]) {
            usedCapacity[chromosome[i]] = customers[i]
        } else {
            usedCapacity[chromosome[i]] = Number(usedCapacity[chromosome[i]]) + Number(customers[i])
        }
    }
    for (let i of Object.keys(usedCapacity)) {
        if (usedCapacity[i] > facilities[i]) {
            return false
        }
    }
    return true
}

/**
 * 从数组中寻找最大的n个元素
 * @param array
 * @param n
 */
function maxN(array, n) {
    // 将一切数组升级成二维数组,二维数组的每一行都有两个元素构成[原一位数组的下标,值]
    let matrix = []
    for (let i=0; i<array.length; i++) {
        matrix.push([i, array[i]])
    }

    // 对二维数组排序
    for (let i=0; i<n; i++) {
        for (let j=1; j<matrix.length; j++) {
            if (matrix[j-1][1] > matrix[j][1]) {
                let temp = matrix[j-1]
                matrix[j-1] = matrix[j]
                matrix[j] = temp
            }
        }
    }

    // 取最大的n个元素
    let maxIndexArray = []
    for (let i=matrix.length-1; i>matrix.length-n-1; i--) {
        maxIndexArray.push(matrix[i][0])
    }

    return maxIndexArray
}

/**
 * 复制(复制上一代中优良的染色体)
 * @param chromosomeMatrix 上一代染色体矩阵
 * @param newChromosomeMatrix 新一代染色体矩阵
 */
function copy(chromosomeMatrix, newChromosomeMatrix) {
    // 寻找适应度最高的N条染色体的下标(N=染色体数量*复制比例)
    let chromosomeIndexArr = maxN(adaptability, chromosomeNum*cp)

    // 复制
    for (let i=0; i<chromosomeIndexArr.length; i++) {
        let chromosome = chromosomeMatrix[chromosomeIndexArr[i]]
        newChromosomeMatrix.push(chromosome)
    }
    return newChromosomeMatrix
}

/**
 * 计算所有染色体的cost
 * @param chromosomeMatrix
 */
function calCost_oneIt(chromosomeMatrix) {
    // 计算每条染色体的cost
    let costArray_oneIt = []
    for (let chromosomeIndex=0; chromosomeIndex<chromosomeNum; chromosomeIndex++) {
        let cost = 0
        let usedFacilities= {}
        for (let facilityIndex=0 ;facilityIndex<facilityNum; facilityIndex++) {
            for (let customerIndex=0; customerIndex<customerNum; customerIndex++) {
                if (chromosomeMatrix[chromosomeIndex][customerIndex] == facilityIndex) {
                    cost = Number(cost) + Number(assignment[customerIndex][facilityIndex])
                    // 已经开启的设施不再加openCost
                    if (!usedFacilities[facilityIndex]) {
                        usedFacilities[facilityIndex] = 1
                        cost = Number(cost) + Number(openCost[facilityIndex])
                    }
                }
            }
        }

        costArray_oneIt.push(cost)
    }
    resultDataGa.push(costArray_oneIt)
    return costArray_oneIt
}

/**
 * 繁衍新一代染色体
 * @param chromosomeMatrix 上一代染色体
 */
function createGeneration(chromosomeMatrix) {

    // 第一代染色体,随机生成
    if (!chromosomeMatrix) {
        let newChromosomeMatrix = []
        for (let chromosomeIndex=0; chromosomeIndex<chromosomeNum; chromosomeIndex++) {
            let chromosomeMatrix_i = []
            let usedCapacity = {}
            for (let customerIndex = 0; customerIndex < customerNum; customerIndex++) {
                let index = random(0, facilityNum-1)
                // 第一次用到这个设施时,加上open cost
                if (!usedCapacity[index]) {
                    usedCapacity[index] = customers[customerIndex]
                }
                // 不可以超过容量限制
                while (usedCapacity[index] + customers[customerIndex] > facilities[index]) {
                    console.log(398)
                    console.log(usedCapacity[index], customers[customerIndex], facilities[index])
                    index = random(0, facilityNum-1)
                }
                usedCapacity[index] = Number(usedCapacity[index]) + Number(customers[customerIndex])
                chromosomeMatrix_i.push(index)
            }
            newChromosomeMatrix.push(chromosomeMatrix_i)
        }

        // 计算当前染色体的cost
        calCost_oneIt(newChromosomeMatrix)
        return newChromosomeMatrix
    }

    // 交叉生成{crossoverMutationNum}条染色体
    let newChromosomeMatrix = cross(chromosomeMatrix)

    // 变异
    newChromosomeMatrix = mutation(newChromosomeMatrix)

    // 复制
    newChromosomeMatrix = copy(chromosomeMatrix, newChromosomeMatrix)

    // 计算当前染色体的任务处理时间
    calCost_oneIt(newChromosomeMatrix)

    return newChromosomeMatrix
}

/**
 * 轮盘赌算法
 * @param selectionProbability 概率数组(下标:元素编号、值:该元素对应的概率)
 * @returns {number} 返回概率数组中某一元素的下标
 */
function RWS(selectionProbability) {
    let sum = 0
    let rand = Math.random()
    for (let i=0; i<selectionProbability.length; i++) {
        sum += selectionProbability[i]
        if (sum >= rand) {
            return i
        }
    }
}


/**
 * 变异
 * @param newChromosomeMatrix 新一代染色体矩阵
 */
function mutation(newChromosomeMatrix) {
    // 随机找一条染色体
    let chromosomeIndex = random(0, crossoverMutationNum-1)

    // 随机找一个顾客
    let customerIndex = random(0, customerNum-1)

    // 随机找一个设施
    let facilityIndex = random(0, facilityNum-1)

    newChromosomeMatrix[chromosomeIndex][customerIndex] = facilityIndex

    while (!checkIfLegal(newChromosomeMatrix[chromosomeIndex])) {
        console.log(461)
        facilityIndex = random(0, facilityNum-1)
        newChromosomeMatrix[chromosomeIndex][customerIndex] = facilityIndex
    }

    return newChromosomeMatrix
}

/**
 * 渲染视图
 * @param resultData
 */
function drawGa(resultData) {
// 基于准备好的dom,初始化echarts实例
    let myChart = echarts.init(document.getElementById('ga'))

    // 指定图表的配置项和数据
    let option = {
        title: {
            text: '基于遗传算法的容量设施问题'
        },
        tooltip : {
            trigger: 'axis',
            showDelay : 0,
            axisPointer:{
                show: true,
                type : 'cross',
                lineStyle: {
                    type : 'dashed',
                    width : 1
                }
            },
            zlevel: 1
        },
        legend: {
            data:[]
        },
        toolbox: {
            show : true,
            feature : {
                mark : {show: true},
                dataZoom : {show: true},
                dataView : {show: true, readOnly: false},
                restore : {show: true},
                saveAsImage : {show: true}
            }
        },
        xAxis : [
            {
                type : 'value',
                scale:true,
                name: '迭代次数'
            }
        ],
        yAxis : [
            {
                type : 'value',
                scale:true,
                name: 'cost'
            }
        ],
        series : [
            {
                name:'遗传算法',
                type:'scatter',
                large: true,
                symbolSize: 3,
                data: (function () {
                    let d = []
                    for (let itIndex=0; itIndex<iteratorNum; itIndex++) {
                        for (let chromosomeIndex=0; chromosomeIndex<chromosomeNum; chromosomeIndex++) {
                            d.push([itIndex, resultDataGa[itIndex][chromosomeIndex]])
                        }
                    }
                    return d
                })()
            }
        ]
    }

    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option)
}

// 蚁群算法
/** 蚂蚁的数量 */
let antNum = 100;


/** 信息素矩阵(记录每条路径上当前信息素含量,初始状态下均为0) */
let pheromoneMatrix = [];

/** 最大信息素的下标矩阵(存储当前信息素矩阵中每行最大信息素的下标) */
let maxPheromoneMatrix = [];

/** 一次迭代中,随机分配的蚂蚁临界编号(该临界点之前的蚂蚁采用最大信息素下标,而该临界点之后的蚂蚁采用随机分配) */
let criticalPointMatrix = [];

/** 顾客处理cost结果集([迭代次数][蚂蚁编号]) */
// let resultData = [];

/** 每次迭代信息素衰减的比例 */
let p = 0.5;

/** 每次经过,信息素增加的比例 */
let q = 2;

/**
 * 渲染视图
 * @param resultData
 */
function drawAca(resultData) {
// 基于准备好的dom,初始化echarts实例
    let myChart = echarts.init(document.getElementById('aca'));

    // 指定图表的配置项和数据
    let option = {
        title: {
            text: '基于蚁群算法的设施容量问题'
        },
        tooltip : {
            trigger: 'axis',
            showDelay : 0,
            axisPointer:{
                show: true,
                type : 'cross',
                lineStyle: {
                    type : 'dashed',
                    width : 1
                }
            },
            zlevel: 1
        },
        legend: {
            data:['传统蚁群算法']
        },
        toolbox: {
            show : true,
            feature : {
                mark : {show: true},
                dataZoom : {show: true},
                dataView : {show: true, readOnly: false},
                restore : {show: true},
                saveAsImage : {show: true}
            }
        },
        xAxis : [
            {
                type : 'value',
                scale:true,
                name: '迭代次数'
            }
        ],
        yAxis : [
            {
                type : 'value',
                scale:true,
                name: '总cost'
            }
        ],
        series : [
            {
                name:'蚁群算法',
                type:'scatter',
                large: true,
                symbolSize: 3,
                data: (function () {
                    let d = [];
                    for (let itIndex=0; itIndex<iteratorNum; itIndex++) {
                        for (let antIndex=0; antIndex<antNum; antIndex++) {
                            d.push([itIndex, resultDataAca[itIndex][antIndex]]);
                        }
                    }
                    return d;
                })()
            },

        ]
    };

    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option);
}

/**
 * 初始化 顾客集合、处理设施集合
 * @param _facilities 设施参数
 * @param _demands 顾客的要求
 * @param _assignmentCost 顾客到指定设施的cost
 */
function startAca(_facilities, _demands, _assignmentCost) {

    // 初始化设施capacity集合

    facilities = _facilities.reduce((all, now) => all.concat(Number(now.split(' ')[0])), [])
    openCost = _facilities.reduce((all, now) => all.concat(Number(now.split(' ')[1])), [])
    customers= _demands.reduce((all, now) => all.concat(now.split(' ').filter(n => n).map(n =>Number(n))), [])

    _assignmentCost.forEach(_assign => {
    	assignment.push(_assign.split(' ').filter(n => n).map(n => Number(n)))
    })

    /* console.log(facilities, openCost, customers, assignment)
    // 执行蚁群算法
    aca();

    // 渲染视图
    draw(resultData);
    // console.log(resultData); */

}

// startGa(lines.slice(1, facilityNum+1), lines.slice(facilityNum+1, facilityNum+1+customerNum/10), lines.slice(facilityNum+1+customerNum/10))
/**
 * 初始化信息素矩阵(全为0)
 * @param customerNum 顾客数量
 * @param facilityNum 设施数量
 */
function initPheromoneMatrix(customerNum, facilityNum) {
    for (let i=0; i<customerNum; i++) {
        let pheromoneMatrix_i = [];
        for (let j=0; j<facilityNum; j++) {
            pheromoneMatrix_i.push(1);
        }
        pheromoneMatrix.push(pheromoneMatrix_i);
    }
}

/**
 * 初始化一个二维数组
 * @param n 行数
 * @param m 列数
 * @param defaultNum 默认值
 */
function initMatrix(n, m, defaultNum) {
    let matrix = [];
    for (let i=0; i<n; i++) {
        let matrix_i = [];
        for (let j=0; j<m; j++) {
            matrix_i.push(defaultNum);
        }
        matrix.push(matrix_i);
    }
    return matrix
}

/**
 * 将第customerCount个顾客分配给某一个设施处理
 * @param antCount 蚂蚁编号
 * @param customerCount 顾客编号
 * @param Object usedCapacity 已经启用的设施
 */
function assignOneCustomer(antCount, customerCount, usedCapacity) {

    // 若当前蚂蚁编号在临界点之前,则采用最大信息素的分配方式
    // 若当前蚂蚁编号在临界点之后,则采用随机分配方式

    let index = criticalPointMatrix.length && antCount <= criticalPointMatrix[customerCount] 
    			? maxPheromoneMatrix[customerCount]
    			: random(0, facilityNum-1)
    
    // 看index设施剩余容量是否还能满足这顾客
    if (!usedCapacity[index]) {
    	usedCapacity[index] = customers[customerCount]
    }

    // console.log(usedCapacity[index], customers[customerCount], facilities[index])

    while (usedCapacity[index]+customers[customerCount] > facilities[index] ) {
    	console.log(281)
    	index = random(0, facilityNum-1)
    }
    usedCapacity[index] = Number(customers[customerCount]) + Number(usedCapacity[index])
    return index
}

/**
 * 计算一次迭代中,所有蚂蚁的顾客处理cost
 * @param pathMatrix_allAnt 所有蚂蚁的路径
 */
function calCost_OneIt(pathMatrix_allAnt) {
    let cost_allAnt = []

    for (let antIndex=0; antIndex<pathMatrix_allAnt.length; antIndex++) {
        // 获取第antIndex只蚂蚁的行走路径
        let pathMatrix = pathMatrix_allAnt[antIndex]

        let cost = 0

        // 记录开门的设施
        let usedFacilities= {}
        for (let facilityIndex = 0; facilityIndex < facilityNum; facilityIndex++) {
            // 计算设施customerIndex的顾客处理cost
            for (let customerIndex = 0; customerIndex < customerNum; customerIndex++) {
            	// 计算一只蚂蚁分配的路线所有花销
                if (pathMatrix[customerIndex][facilityIndex]) {
                    cost += assignment[customerIndex][facilityIndex]
                    if (!usedFacilities[facilityIndex]) {
                    	usedFacilities[facilityIndex] = 1
                    	cost = Number(cost) + Number(openCost[facilityIndex])
                    } 
                }
            }
        }

        cost_allAnt.push(cost)
    }

    return cost_allAnt
}

/**
 * 更新信息素
 * @param pathMatrix_allAnt 本次迭代中所有蚂蚁的行走路径
 * @param pheromoneMatrix 信息素矩阵
 * @param costArray_oneIt 本次迭代的顾客处理cost的结果集
 */
function updatePheromoneMatrix(pathMatrix_allAnt, pheromoneMatrix, costArray_oneIt) {
    // 所有信息素均衰减p%
    for (let i = 0; i < customerNum; i++) {
        for (let j = 0; j < facilityNum; j++) {
            pheromoneMatrix[i][j] *= p;
        }
    }

    // 找出总cost最小的蚂蚁编号
    let mincost = Number.MAX_VALUE;
    let minIndex = -1;
    for (let antIndex = 0; antIndex < antNum; antIndex++) {
        if (costArray_oneIt[antIndex] < mincost) {
            mincost = costArray_oneIt[antIndex];
            minIndex = antIndex;
        }
    }

    // 将本次迭代中最优路径的信息素增加q%
    for (let customerIndex = 0; customerIndex < customerNum; customerIndex++) {
        for (let facilityIndex = 0; facilityIndex < facilityNum; facilityIndex++) {
            if (pathMatrix_allAnt[minIndex][customerIndex][facilityIndex] == 1) {
                pheromoneMatrix[customerIndex][facilityIndex] *= q;
            }
        }
    }


    maxPheromoneMatrix = [];
    criticalPointMatrix = [];
    for (let customerIndex = 0; customerIndex < customerNum; customerIndex++) {
        let maxPheromone = pheromoneMatrix[customerIndex][0]
        let maxIndex = 0
        let sumPheromone = pheromoneMatrix[customerIndex][0]
        let isAllSame = true

        for (let facilityIndex = 1; facilityIndex < facilityNum; facilityIndex++) {
            if (pheromoneMatrix[customerIndex][facilityIndex] > maxPheromone) {
                maxPheromone = pheromoneMatrix[customerIndex][facilityIndex]
                maxIndex = facilityIndex
            }
            if (pheromoneMatrix[customerIndex][facilityIndex] != pheromoneMatrix[customerIndex][facilityIndex-1]){
                isAllSame = false
            }
            sumPheromone += pheromoneMatrix[customerIndex][facilityIndex]
        }

        // 若本行信息素全都相等,则随机选择一个作为最大信息素
        if (isAllSame==true) {
            maxIndex = random(0, facilityNum-1)
            maxPheromone = pheromoneMatrix[customerIndex][maxIndex]
        }

        // 将本行最大信息素的下标加入maxPheromoneMatrix
        maxPheromoneMatrix.push(maxIndex)

        // 将本次迭代的蚂蚁临界编号加入criticalPointMatrix(该临界点之前的蚂蚁的顾客分配根据最大信息素原则,而该临界点之后的蚂蚁采用随机分配策略)
        criticalPointMatrix.push(Math.round(antNum * (maxPheromone/sumPheromone)))
    }
}


/**
 * 迭代搜索
 * @param iteratorNum 迭代次数
 * @param antNum 蚂蚁数量
 */

function acaSearch(iteratorNum, antNum) {
    for (let itCount=0; itCount<iteratorNum; itCount++) {

        // 本次迭代中,所有蚂蚁的路径
        let pathMatrix_allAnt = []

        for (let antCount=0; antCount<antNum; antCount++) {
        	let usedCapacity= {}
        	// console.log(antCount)
            // 第antCount只蚂蚁的分配策略(pathMatrix[i][j]表示第antCount只蚂蚁将i顾客分配给j设施处理)
            let pathMatrix_oneAnt = initMatrix(customerNum, facilityNum, 0)

            for (let cCount=0; cCount<customerNum; cCount++) {
            	// console.log(cCount)
                // 将第cCount个customer分配给第fcount个facility处理
                let fCount = assignOneCustomer(antCount, cCount, usedCapacity)
                pathMatrix_oneAnt[cCount][fCount] = 1

            }

            // 将当前蚂蚁的路径加入pathMatrix_allAnt
            pathMatrix_allAnt.push(pathMatrix_oneAnt)

        }

        // 计算 本次迭代中 所有蚂蚁 的花销
        let costArray_OneIt = calCost_OneIt(pathMatrix_allAnt)
        // 将本地迭代中 所有蚂蚁的 cost加入总结果集
        resultDataAca.push(costArray_OneIt)

        // 更新信息素
        updatePheromoneMatrix(pathMatrix_allAnt, pheromoneMatrix, costArray_OneIt)
    }
}

/**
 * 蚁群算法
 */
function aca() {
    // 初始化信息素矩阵
    initPheromoneMatrix(customerNum, facilityNum);
    // 迭代搜索
    acaSearch(iteratorNum, antNum);
}

样例结果

instances 蚁群 result time (ms) 遗传result time(ms)
p1 19372 1600 16678 238
p2 17317 2703 14778 228
p3 21441 1234 15478 232
p4 21599 2666 17030 231
p5 19611 2168 16337 340
p6 17155 2159 17121 272
p7 17467 4542 19125 260
p8 21287 2139 20155 277
p9 19448 480 14098 288
p10 19516 407 12950 281
p11 18826 563 16505 233
p12 20569 1460 16903 230
p13 24688 296 18173 322
p14 19262 454 15937 240
p15 23867 390 19658 243
p16 29112 329 23100 238
p17 24209 246 18957 254
p18 21592 230 13945 237
p19 24621 259 18640 232
p20 27110 273 20089 233
p21 20251 244 15620 239
p22 20937 228 16458 240
p23 24175 302 17608 234
p24 27554 363 22115 243
p25 86414 1121 68169 678
p26 85532 733 61299 694
p40 97156 677 75392 711
p41 13944 939 11616 247
p42 17048 414 13728 338
p43 17932 410 15513 389
p44 17886 1203 13675 262
p45 18612 542 16218 319
p46 21161 368 15371 360
p47 16224 1050 13413 266
p48 18334 507 14585 327
p49 18470 402 15538 377
p50 18355 915 15901 269
p51 22212 555 18516 386
p52 22301 1522 19970 269
p53 26891 715 23231 389
p54 20466 1632 17575 271
p55 25114 740 20959 394
p56 81243 1194 70821 891
p57 88284 1412 80706 876
p58 108619 1149 100460 864
p59 92969 1883 85197 869
p60 80730 983 70333 887
p61 92013 942 81345 909
p62 107893 972 101119 858
p63 93974 959 86221 854
p64 80396 957 69039 860
p65 89248 970 74799 886

相关文章:

  • 2021-08-22
  • 2021-12-06
  • 2021-04-15
  • 2021-06-25
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-10-20
猜你喜欢
  • 2021-03-31
  • 2021-05-08
  • 2021-07-13
  • 2021-10-16
  • 2022-12-23
  • 2022-12-23
  • 2021-10-03
相关资源
相似解决方案