1

私は単純なパズル ゲームの AI を行っており、次の問題を効率的に解決する必要があります (ゲームで多くの反復を行う必要があるため、指定された範囲で 1 秒未満)。

正方形 (0 ~ 200,000,000) の各辺に、強度が 1 ~ 10,000 の N (1 ~ 100,000) のモンスターのサンプルが、左上隅から 1 単位間隔で配置されています。モンスターまでの加重距離の合計が最大になるように、ヒーローを正方形の点 X に移動します。各モンスターまでの加重距離は、MonsterStrength*ShortestDistanceToX (時計回りまたは反時計回り) によって計算されます。X も 1 単位間隔マーク上にある必要があり、モンスターとヒーローは正方形の側面のみを移動します

いくつかのアプローチを試しましたが、どれも十分に高速でも正確でもありません。

この問題を補完する可能性がある (元のセット内の対応する各モンスターから最も遠い点のセットまでの距離の合計を最小化する) は、幾何学的中央値、施設の位置の問題、ウェーバーの問題などを見つけることに関連しているようです。

線形計画法も可能ですが、遅すぎてやり過ぎになる可能性があります。

誰かが良いアプローチのアイデアを持っていますか?


長さ 3 の辺の正方形の図を次に示します。

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

強度 3 のモンスターを 2 に、強度 1 のモンスターを 4 に、強度 2 のモンスターを 12 に、強度 1 のモンスターを 11 に、ヒーロー (X) を 8 にすると、加重距離の合計は次のようになります: 3*6 + 1*4 + 1*3 + 2*4 = 33、これはこの場合の最大値でもあります

4

2 に答える 2

0

興味のある方のために、Crferreira のアイデアをインターバル トラバーサルと共に使用するアルゴリズムのユニット テスト用の C/C++ コードを示します。入力フォーマット: NL Monster1_pos Monster1_strength monster2_pos monster2_strength .... 正確
性は、より小さなケースでブルートフォース アルゴリズムに対してテストされます
大きなケースはランダムに生成され
ます 最大のテスト ケースでは、Ubuntu Linux を実行する 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 に答える
0

必要な 1 秒の応答時間を達成するために従うことができる戦略を指摘しようとします。もちろん、この要件を満たすように実装する必要があります。

解決策は、次の事実に依存しています。

基本的に、位置 P の加重距離 WP の合計が与えられると、各モンスターは、その強さの 1 倍を WP に加算または減算することによって、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 に答える