1

我正在为一个简单的谜题游戏做 AI,需要有效地解决以下问题(指定范围不到 1 秒,因为我需要在游戏中进行多次迭代)。

从左上角开始,以 1 个单位间隔将 N(1 到 100,000)个强度从 1 到 10,000 的怪物样本分布在正方形(0 到 200,000,000)的边上。将英雄移动到正方形上的 X 点,以最大化到怪物的加权距离总和。到每个怪物的加权距离由 MonsterStrength*ShortestDistanceToX 计算(顺时针或逆时针方向)。X 也必须在 1 个单位的间隔标记上,并且怪物和英雄只能在方格的两侧移动

我尝试了几种方法,但没有一种方法足够快或足够准确。

这个问题的可能补充(最小化与原始集中每个相应怪物最远距离的点集的距离总和)似乎与寻找几何中位数、设施位置问题、韦伯问题等有关。

线性编程也是可能的,但可能太慢且过度杀伤力。

有没有人知道一个好的方法?


这是一个边长为 3 的正方形的插图:

1--------2(M/3)--------3-----4(M/1)
| |
12(中/2) 5
| |
11(中/1) 6
| |
10--------9---------8(X)--------7

如果你把一个力量3的怪物放在2,一个力量1的怪物放在4,一个力量2的怪物放在12,一个力量1的怪物放在11,英雄(X)放在8。加权距离之和为:3*6 + 1*4 + 1*3 + 2*4 = 33,这也是本例中的最大值

4

2 回答 2

0

我将尝试指出您可以遵循的策略,以实现所需的 1 秒响应时间。当然,它必须被实施以确保它符合这个要求。

该解决方案依赖于以下事实:

基本上,给定位置 P 的加权距离 WP 总和,每个怪物将通过增加或减去 WP 的 1 倍强度来对 P 邻居的加权距离总和做出贡献。如果邻居比 P 更接近怪物,则增加力量,如果距离更远,则减少力量。

考虑到这一事实,解决方案在于计算初始步骤上某个初始位置的加权距离之和,并根据先前为其邻居计算的值计算其他位置的加权距离之和。

除了计算初始位置的值外,我们还必须在初始步骤中定义:

  • 一个方向(例如顺时针),我们将在该方向上遍历位置以计算加权距离的总和
  • 在定义的方向上穿越时,我们将获得更远(添加)的怪物的力量总和(SADD);
  • 在定义的方向上穿越时,我们将更接近(减去)的怪物的力量总和(SSUB);

然后,从初始位置的邻居开始,遍历定义方向上的所有位置,并且对于每个位置,我们更新 SADD 和 SSUB(当我们遍历圆形路径时,一些越来越近的怪物开始变得越来越远,反之亦然反之亦然)并将 (SADD - SSUB) 添加到为前一个邻居计算的值上。

因此,我们可以计算所有位置的加权距离之和,而不必为每个位置遍历所有怪物。

我用 Java 实现了该解决方案的初始版本。

下面的类代表一个怪物:

class Monster {
    private long strenght;
    private int position;
    // omitting getters and setters...        
}

下面的类代表正方形边的位置:

class SquareSidePositions {
    private List<Monster>[] positionWithMosters;
    private List<Monster> monstersOnSquareSides = new ArrayList<Monster>();

    @SuppressWarnings("unchecked")
    public SquareSidePositions(int numberOfPositions) {
        positionWithMosters = new LinkedList[numberOfPositions];
    }

    public void add(int position, Monster monster) {
        if (positionWithMosters[position] == null) {
            positionWithMosters[position] = new LinkedList<Monster>();
        }

        positionWithMosters[position].add(monster);
        monster.setPosition(position);
        monstersOnSquareSides.add(monster);
    }

    public int size() {
        return positionWithMosters.length;
    }

    public boolean hasMonsters(int position) {
        return positionWithMosters[position] != null;
    }

    public long getSumOfStrenghtsOfMonstersOnThePosition(int i) {
        long sum = 0;

        for (Monster monster : positionWithMosters[i]) {
            sum += monster.getStrenght();
        }

        return sum;
    }

    public List<Monster> getMonstersOnSquareSides() {
        return monstersOnSquareSides;
    }
}

最后,通过以下方法进行优化:

public static int findBest(SquareSidePositions positions) {
    long tini = System.currentTimeMillis();
    long sumOfGettingNearer = 0;
    long sumOfGettingFarther = 0;
    int currentBestPosition;
    long bestSumOfWeight = 0;
    long currentSumOfWeight;
    final int numberOfPositions = positions.size();
    int halfNumberOfPositions = numberOfPositions/2;
    long strenghtsOnPreviousPosition = 0;
    long strenghtsOnCurrentPosition = 0;
    long strenghtsOnPositionStartingGetNearer = 0;
    int positionStartGetNearer;

    // initial step. Monsters from initial position (0) are skipped because they are at distance 0 
    for (Monster monster : positions.getMonstersOnSquareSides()) {
        // getting nearer
        if (monster.getPosition() < halfNumberOfPositions) {
            bestSumOfWeight += monster.getStrenght()*monster.getPosition();
            sumOfGettingNearer += monster.getStrenght();
        } else {
        // getting farther
            bestSumOfWeight += monster.getStrenght()*(numberOfPositions - monster.getPosition());
            sumOfGettingFarther += monster.getStrenght();
        }
    }

    currentBestPosition = 0;
    currentSumOfWeight = bestSumOfWeight;

    // computing sum of weighted distances for other positions
    for (int i = 1; i < numberOfPositions; ++i) {
        strenghtsOnPreviousPosition = 0;
        strenghtsOnPositionStartingGetNearer = 0;
        strenghtsOnCurrentPosition = 0;
        positionStartGetNearer = (halfNumberOfPositions + i - 1);

        if (positionStartGetNearer >= numberOfPositions) {
            positionStartGetNearer -= numberOfPositions;
        }

        // monsters on previous position start to affect current and next positions, starting to get farther
        if (positions.hasMonsters(i-1)) {
            strenghtsOnPreviousPosition = positions.getSumOfStrenghtsOfMonstersOnThePosition(i-1);  
            sumOfGettingFarther += strenghtsOnPreviousPosition;
        }

        // monsters on current position will not affect current position and stop to get nearer
        if (positions.hasMonsters(i)) {
            strenghtsOnCurrentPosition = positions.getSumOfStrenghtsOfMonstersOnThePosition(i);
            currentSumOfWeight -= strenghtsOnCurrentPosition;
            sumOfGettingNearer -= strenghtsOnCurrentPosition;
        }

        // monsters on position next to a half circuit start to get nearer
        if (positions.hasMonsters(positionStartGetNearer)) {
            strenghtsOnPositionStartingGetNearer = positions.getSumOfStrenghtsOfMonstersOnThePosition(positionStartGetNearer);
            sumOfGettingNearer += strenghtsOnPositionStartingGetNearer;
            sumOfGettingFarther -= strenghtsOnPositionStartingGetNearer;
        }

        currentSumOfWeight += sumOfGettingFarther - sumOfGettingNearer;

        // if current is better than previous best solution
        if (currentSumOfWeight > bestSumOfWeight) {
            bestSumOfWeight = currentSumOfWeight;
            currentBestPosition = i;
        }
    }

    final long executionTime = System.currentTimeMillis() - tini;

    System.out.println("Execution time: " + executionTime + " ms");
    System.out.printf("best position: %d with sum of weighted distances: %d\n", currentBestPosition, bestSumOfWeight);

    return currentBestPosition;
}

要设置您用作示例的输入,您可以使用:

SquareSidePositions positions = new SquareSidePositions(12);

positions.add(1, new Monster(3));
positions.add(3, new Monster(1));
positions.add(10, new Monster(1));
positions.add(11, new Monster(2));

在初步测试中,该方法在运行 Windows 7 的 Intel Core i5-2400 上执行 100,000 个怪物和 200,000,000 个可能位置需要 771 毫秒。

我使用以下代码来生成此输入:

// I used numberOfMosters == 100000 and numberOfPositions == 200000000
public  static SquareSidePositions initializeMonstersOnPositions(int numberOfMonsters, int numberOfPositions) {
    Random rand = new Random();
    SquareSidePositions positions = new SquareSidePositions(numberOfPositions);

    for (int i = 0; i < numberOfMonsters; ++i) {
        Monster monster = new Monster(rand.nextInt(10000)+1);

        positions.add(rand.nextInt(numberOfPositions), monster);
    }

    return positions;
}

我希望它对你有帮助!

于 2013-08-07T17:04:29.303 回答
0

对于任何有兴趣的人,这里是我的 C/C++ 代码,用于对使用 Crferreira 的想法和区间遍历的算法进行单元测试。输入格式:NL monster1_pos monster1_strength monster2_pos monster2_strength ....
正确性在较小情况下针对暴力算法进行测试
随机生成大型情况
在 Intel core i3 上运行 44 毫秒到 84 毫秒的最大测试用例

#include <stdio.h>
#include <stdlib.h>

#define MAXH 100000

int num_monster, side;
int half, total;
int monster[MAXH][2]; //col0: monster pos, col1: strength
int opp[MAXH][3]; //col0: opp pos, col1: num people in opp monster, col2: index of opp monster
int boundaryMonster = -1;

int min (int a, int b) {
    return a<b?a:b;
}

int getOpp(int pos) {
    return (pos==half)?total:(pos+half)%total;
}

int getDist(int from, int to) {
    return min(abs(to-from), total-abs(to-from));
}

int totalsum(int pos) {
    int result = 0;
    for (int i = 0; i < num_monster; i++) {
        result += getDist(pos, monster[i][0])*monster[i][1];
    }
    return result;
}

//find sorted sequence of pos where monster exists at opposite pos
void oppSeq() {
    int count = 0;
    for (int i = boundaryMonster; i < num_monster; i++) {
        opp[count][0] = getOpp(monster[i][0]);
        opp[count][1] = monster[i][1];
        opp[count][2] = i;
        count++;
    }
    for (int i = 0; i < boundaryMonster; i++) {
        opp[count][0] = getOpp(monster[i][0]);
        opp[count][1] = monster[i][1];
        opp[count][2] = i;
        count++;
    }
}

int main() {
    FILE *input, *output;

    input = fopen("monster.in", "r");
    output = fopen("monster.out", "w");

    fscanf(input, "%d %d", &num_monster, &side);
    for (int i = 0; i < num_monster; i++) {
        fscanf(input, "%d %d", &monster[i][0], &monster[i][1]);
        if (boundaryMonster == -1 && monster[i][0] >= (1+2*side))
            boundaryMonster = i;
    }

    fclose(input);

    if (num_monster == 0) { 
        fprintf(output, "%d", 0); 
        fclose(output);
        return 0; 
    }

    half = 2*side;
    total = 4*side;

    oppSeq();

    int cur_sum = totalsum(1);
    int cur_monster = 0, cur_opp = 0;
    int prev_pos = 1;

    int delta = 0;
    for (int i = 0; i < num_monster; i++) {
        int mid = 1+half;
        if (monster[i][0] > 1 && monster[i][0] <= mid) 
            delta -= monster[i][1];
        else delta += monster[i][1];
    }

    if (monster[0][0] == 1) cur_monster = 1;
    if (opp[0][0] == 1) cur_opp = 1;

    int best = cur_sum;
    while (cur_monster < num_monster || cur_opp < num_monster) {
        if (cur_monster < num_monster && cur_opp < num_monster) {
            //going clockwise with both `monster` and `opp` *similar to merge sort merge phase
            if (monster[cur_monster][0] < opp[cur_opp][0]) {
                //update sum going from prev to cur_monster
                cur_sum += delta*(monster[cur_monster][0]-prev_pos);
                //start moving away from cur_monster->update delta
                delta += 2*monster[cur_monster][1];
                prev_pos = monster[cur_monster][0];
                cur_monster++;
            } else if (opp[cur_opp][0] < monster[cur_monster][0]) {
                cur_sum += delta*(opp[cur_opp][0]-prev_pos);
                //starting moving towards opposite monster
                delta -= 2*monster[ opp[cur_opp][2] ][1];
                prev_pos = opp[cur_opp][0];
                cur_opp++; 
            } else if (opp[cur_opp][0] == monster[cur_monster][0]) {
                cur_sum += delta*(monster[cur_monster][0]-prev_pos);
                //starting towards opp monster and away from current monster;
                delta += 2*(monster[cur_monster][1] - monster[ opp[cur_opp][2] ][1]);
                prev_pos = monster[cur_monster][0];
                cur_opp++; cur_monster++;
            }
        } else if (cur_monster < num_monster) {
            cur_sum += delta*(monster[cur_monster][0]-prev_pos);
            delta += 2*monster[cur_monster][1];
            prev_pos = monster[cur_monster][0];
            cur_monster++;
        } else {
            cur_sum += delta*(opp[cur_opp][0]-prev_pos);
            delta -= 2*monster[ opp[cur_opp][2] ][1];
            prev_pos = opp[cur_opp][0];
            cur_opp++; 
        }
        if (cur_sum > best) best = cur_sum;
    }

    fprintf(output, "%d", best);
    fclose(output);

    return 0;
}
于 2013-08-08T04:06:11.993 回答