simanのブログ

ゆるふわプログラマー。競技プログラミングやってます。Ruby好き

Marathon Match 130 GraphLabeling Strategy

Problem

www.topcoder.com

Note

This article has been translated by DeepL. Original ver is here.

https://simanman.hatenablog.com/entry/2021/11/03/121958

Strategy

The general strategy is to determine the maximum value for labeling before, and then gradually reduce the maximum value if the entire node was successfully labeled, in a binary search-like strategy.

  • Pre-determine the maximum value of label X to be given before labeling.
  • Start labeling from a random point and visit each node recursively
  • When labeling at each vertex, check if nodes with an even distance from the starting point can be labeled from 0 (0, 1, 2, ..., X). Nodes with odd distances are checked to see if they can be labeled from X (X, X - 1, X - 2, ..., 0).
  • If labeling succeeds for all nodes, update the maximum X value (X = X / 2); if it fails, set a value slightly lower than the previous best value (X = BEST_X * random_value [0.95, 1.0))
  • Repeat this procedure until the time limit is reached.

Determine the maximum value X of the label

Determine the maximum value when labeling is done. This way you will get a graph with a definite X score when the labeling is complete.

Labeling

Start from a random point and label it. Nodes with an even distance from the starting point will search for labels assignable from 0, and nodes with an odd distance will search for labels assignable from X.

Example for X = 100

f:id:simanman:20211103120144p:plain:w240

f:id:simanman:20211103120719p:plain:w240

f:id:simanman:20211103120707p:plain:w240

f:id:simanman:20211103120826p:plain:w240

f:id:simanman:20211103120925p:plain:w240

f:id:simanman:20211103121016p:plain:w240

Since the labeling was successful for X = 100, we will do the same for the next attempt with the value of X = 50. If it fails, we randomly choose a value from the range X = [95, 99]. If it succeeds, it will lower the maximum value by a large amount, and if it fails, it will lower it by a small amount.

Repeat this process until the time limit and output the best labeling.

Summary

My overall score was low because I didn't know about the Gorom ruler, but I was able to get a high score on a sparse graph, so I put together a strategy.

Marathon Match 130 GraphLabeling 戦略

問題

www.topcoder.com

戦略

大まかな戦略としてはラベル付けを行う際の最大値を事前に決定して、ノード全体にラベル付けが成功した場合は最大値を徐々に減らしていくという二分探索風な戦略を取りました。

  • ラベル付けを行う前に与えるラベルの最大値 X を事前に決定する
  • 任意の点からラベル付けを開始し、各ノードへ再帰的に訪問する
  • 各頂点においてラベル付けを行う際に、開始地点からの距離が偶数のノードは 0 からラベル付けが行えるかどうかをチェックする (0, 1, 2 ... X)。距離が奇数のノードは X からラベル付けが行えるかどうかをチェックする (X, X - 1, X - 2, ... 0)。
  • すべてのノードに対してラベル付けが成功した場合は最大値 X の値を更新する(X = X / 2)、失敗した場合は過去のベストの値から少し下げた値を設定する(X = BEST_X * random_value [0.95, 1.0))
  • この手順を制限時間まで繰り返す

ラベルの最大値 X を決定する

ラベル付けを行う際に最大値を決定します。こうすることでラベル付けが完了した際に X スコアが確定したグラフを手に入れることが出来ます。

ラベル付けを行う

任意の点から開始してラベル付けを行います。開始地点から距離が偶数のノードは 0 から割り振り可能なラベルを探索、距離が奇数のノードは X から割り振り可能なラベルの探索を行います。

X = 100 の例

f:id:simanman:20211103120144p:plain:w240

f:id:simanman:20211103120719p:plain:w240

f:id:simanman:20211103120707p:plain:w240

f:id:simanman:20211103120826p:plain:w240

f:id:simanman:20211103120925p:plain:w240

f:id:simanman:20211103121016p:plain:w240

X = 100 ではラベル付けが成功したので、次の試行では X = 50 の値で同じ処理を行います。もし失敗した場合は X = [95, 99] の範囲からランダムに選びます。成功したときは大きく最大値を下げて、失敗した場合は小さく下げていく感じになります。

この処理を制限時間まで繰り返して一番良かったラベル付けを出力します。

まとめ

ゴロム定規を知らなかったので全体的なスコアは低かったのですが疎なグラフでは高いスコアを出せていたので方針をまとめてみました。

Marathon Match 129 ChickenRun (translated ver)

Prolog

This article is translated by DeepL and fix a little.

simanman.hatenablog.com

Problem

www.topcoder.com

Strategy

  • Organize the farmers into groups of two. If there is an odd number, only one group will have three members.
  • Move the farmer.
  • Evaluate the grid after the move to determine the best farmers placement.

Farmer groupings

We will need at least two people to catch the chickens, so build a group of two.(There are some patterns that can be caught by one person, but that's not many.)

f:id:simanman:20210923220118p:plain:w480

f:id:simanman:20210923220132p:plain:w480

If there is an odd number, there will be only one group of three. Each group decides on a chicken to chase, and then tries to chase.

Move the farmers

As for moving farmers, there are five possible move candidates for each farmer: up, down, right, left, and wait. If you try to combine the moves of each farmer, you will get a pattern of 5 ** number of farmers. In order to find the optimal placement from this vast of patterns, I used the direction of movement of the farmers as an array and SA (Simulated Annealing) the direction of movement to find the optimal placement.

f:id:simanman:20210924055628p:plain:w480

The transition is select any one point and change direction.

f:id:simanman:20210924055532p:plain:w480

This will make it easier to search for some effective patterns even if the number of farmers increases.

Evaluation of the grid

For the evaluation of the board, we focused on the following items for confirmation.

  • Catching the chickens
  • Check to see if the chickens are in a caught pattern.
  • Whether you are able to get close to the chicken to be captured or not.
  • Whether or not the candidates of movement of the chickens to be captured is being reduced.

The higher the likelihood of catching chickens, the higher the evaluation value.

Catching the chickens

If farmer reaches the same cell as the chicken that is the highest value. In some cases, it may be better not to dare to catch them, but I couldn't evaluate them that much.

A case where it's better not to catch them.

f:id:simanman:20210923222127p:plain:w480

The best solution here will be to try to catch two chickens at the same time.

f:id:simanman:20210923222231p:plain:w480

Patterns that can be caught

Condition of a chicken can be caught is the chicken cannot move and there is a farmer in the adjacent cell to the chicken. For each chicken, check if it is in this state, and if it matches the pattern, give it a higher value. The main patterns are as follows.

f:id:simanman:20210923223028p:plain:w600

Leading to a catching pattern

For the patterns that can be caught, the prior state is only one candidate for chicken movement and a fence or farmer exists at the destination, so this state is also evaluated.

f:id:simanman:20210923224255p:plain:w600

Patterns that can be caught with probability

Since both the order and direction of the chickens' actions are randomly determined, the order and direction of the chickens' movements may lead to a catching pattern, also evaluate those patterns.

A pattern that can be caught depending on the order in which the chickens move.

f:id:simanman:20210923231041p:plain:w600

A pattern that can be caught depending on where the chickens are moved.

f:id:simanman:20210923231350p:plain:w600

Approach the target chicken.

Approach the target chicken. When approaching, move in such a way that you can reach as many cells as possible before the target chicken.(Move the chicken so that its Voronoi area becomes smaller.)

Summary

I made a mistake in the design of the evaluate function because the content of the evaluation function to catch the chickens became complicated and produced some unintended behavior. As for the movement of farmers, I think it would have been more efficient to focus on a specific area rather than the whole area at once, but I didn't have time to implement it, so I gave up. I was a little put off by the randomness of the chicken movement, but I wish I had done a little more detailed research and incorporated it into my evaluate function.

Marathon Match 129 ChickenRun

問題

www.topcoder.com

方針

  • 農家を 2人 1グループにまとめる。奇数の場合は 1つだけ3人のグループになる。
  • 農家を移動させる
  • 移動後の盤面を評価して一番良い配置を決定する

農家のグループ分け

鶏の確保には最低でも 2人必要なので 2人組のグループを作成します。(1人でも確保出来るパターンは存在しますがそんなに多くありません)

f:id:simanman:20210923220118p:plain:w480

f:id:simanman:20210923220132p:plain:w480

奇数の場合は 1つだけ 3人のグループが出来ます。 各グループごとに追いかける鶏を決定し、その鶏を追いかけるようにします。

農家の移動

農家の移動ですが、各農家の移動先の候補は , , , , 待機 の 5つの候補が存在しており、各農家の移動の組み合わせを試そうとした場合 5 ** 農家の人数 のパターンが発生します。この膨大なパターンの中から最適な配置を見つけるために農家の移動方向を配列としてその移動方向を焼き鈍すことで最適な配置を求めました。

f:id:simanman:20210923213836p:plain:w480

遷移は任意の1箇所を選んで方向を変更するです

f:id:simanman:20210923213940p:plain:w480

これで農家が増えてもある程度有効なパターンを探索しやすくなります。

盤面の評価

盤面の評価は以下の項目を中心に確認を行いました。

  • 鶏の確保
  • 鶏が確保できるパターンかどうかの確認
  • 捕獲対象の鶏に接近できているかどうか
  • 捕獲対象の鶏の移動可能範囲を狭めているかどうか

鶏の確保に繋がる可能性が高いほどより高い評価になるようにしています。

鶏の確保

鶏と同じマスに到達した場合はスコアが増えるので評価としては一番高いです。場合によってはあえて捕まえないほうが良くなる場合もありますが、そこまで評価出来ませんでした。

捕まえないほうが良くなるケース

f:id:simanman:20210923222127p:plain:w480

二羽同時に捕まえられるようにするのがここでは最適解となります。

f:id:simanman:20210923222231p:plain:w480

確保できるパターン

鶏が確保できる状態というのは 鶏が移動不可 + 鶏の隣接マスに農家が存在する ことを指します。各鶏についてこの状態になっているかどうかを調べ、そのパターンに一致した場合は評価を高くします。主なパターンとしては以下のようなものがあります。

f:id:simanman:20210923223028p:plain:w600

確定パターンへの誘導

確保出来るパターンについては事前の状態として 鶏の移動候補が 1つ + 移動先に柵または農家が存在している という状態になっているので、この状態についても評価を行います。

f:id:simanman:20210923224255p:plain:w600

確率で確保出来るパターン

鶏の行動は行動する順番も方向もランダムに決定されるので、鶏の移動順や移動方向によっては確定パターンにつながる可能性があります。それらのパターンについても評価を行います。

鶏の移動順によっては確保が可能になるパターン。

f:id:simanman:20210923231041p:plain:w600

鶏の移動先によっては確保が可能になるパターン。

f:id:simanman:20210923231350p:plain:w600

対象の鶏に接近する

対象の鶏に対して接近します。接近する際には対象の鶏より先に到達出来るマスの数を多くするように移動します。(鶏のボロノイ領域が小さくなるように移動する)

まとめ

鶏を確保するための評価関数の中身が複雑になって意図しない挙動も出てしまったので評価関数の設計で失敗してしまった感じ。農家の移動に関しては全体をまとめてやるより特定の領域とかで絞ったほうがもっと効率的に配置を調べられた気もするけど実装する時間がなかったので諦めた。鶏の移動についてランダム性が高いのでちょっと後回しにしてたけど、もうちょっと細かく調査して評価に組み込めば良かったなと思う。

RECRUIT 日本橋ハーフマラソン 2021〜増刊号〜 参加記

問題

atcoder.jp

方針

  • 機械は連結を維持する、ただし序盤は非連結をある程度許容する
  • 野菜の「出現位置」「出現時間」を元にヒートマップを作成してそれを元に機械の配置を決めていく
  • 探索で使用しているパラメータを変化させながらシミュレーションを複数回実行して一番良い結果を得られたものを採用する

ヒートマップの作成

探索の際に「どの位置に機械を配置するか」「どの野菜を回収するか」を決定するためにシミュレーション実行前に作成を行う。 作成方法については実際の出現時間を変更して早めに野菜が出現しているような状態を表現しました。

例えば出現時間が 7 ~ 9 の野菜に対して 4 ~ 9 のように出現時間を早めています。

f:id:simanman:20210913161657p:plain:w400

f:id:simanman:20210913193639p:plain:w400

こうすることで、まだ出現していない野菜に対しても事前に機械を先置き出来るようになります。

収穫する野菜の決定

収穫する野菜について

  • 野菜の価値
  • 周辺のヒートマップの値の合計値
  • 野菜消失までの時間
  • 野菜までの距離

の 4つの要素から決定しました。 コードとして色々なパラメータが絡んでいるので説明し辛いのですが、基本的には「価値 / 距離」をベースにそこから消失までの残り時間を見て、収穫を後回しにしても良いかどうかを評価値に含んでいる感じです。

収穫する野菜までの経路作成

収穫する野菜を決定したら、その野菜をスタート地点としてダイクストラでヒートマップの価値が高い順に探索していき経路を作成しました、ただし経路自体は最短経路になるようにしています。(スタートからの距離のコストを高くしている)

機械の移動

経路を作成後、機械の移動を行います。移動の際には連結を崩さないように移動しても連結が崩れない機械を探索します。この部分については特に難しいことはしておらず、素直にその機械を実際に消して連結状態が維持されているかどうかを再帰探索で調べています。

f:id:simanman:20210913165519p:plain:w400

移動可能な機械を列挙する

f:id:simanman:20210913165624p:plain:w400

列挙後、移動可能な機械の中でヒートマップの値が一番小さいものを移動させます。(ヒートマップの値が高い場合はその後そこに野菜が来る可能性があるのでなるべくその位置に機械を配置するようにする)

f:id:simanman:20210913165858p:plain:w400

この作業を目標の野菜に到達するまで繰り返します。

例外

移動に関して、目標の野菜までの距離が現在の機械の数よりも大きい場合は連結を解除して野菜を収穫しに行きます。

f:id:simanman:20210913170437p:plain:w400

f:id:simanman:20210913170648p:plain:w400

f:id:simanman:20210913170543p:plain:w400

シミュレーションを複数回実行

このシミュレーションを時間制限いっぱい、探索に使用しているパラメータを変化させながらシミュレーションを複数回実行して、その中で一番良い結果が得られたものを採用しています。主に変化させたパラメータとしては以下になります。

  • 購入する機械の数
  • 消失までの残り時間の重み
  • 周辺のヒートマップの重み
  • ヒートマップ作成時の野菜の出現時間をどれだけ早めるか

動作状況

実際どんな感じで動いているかのキャプチャです

f:id:simanman:20210913173851g:plain

My approach of MM126 Slider

It was a matter of efficiently dropping the blocks into the holes.

https://www.topcoder.com/challenges/82eb883d-85e9-442f-b0eb-91631d3d2a8f

方針

First, create an operation array in the order of dropping the base block.

There are several steps involved in creating an operation array.

Improving efficiency of 1-move drop

The first step is to list the blocks that can be dropped in one move, and find the order in which they are dropped by SA.

Since the operation of moving a block affects the operations before and after it, when the order of operations is changed, it is necessary to check each time whether the current operations is valid or not, and this cost is so large that it is difficult to do it.

However, if the order of the moves is reduced to [position of the hole, direction of block acquisition], there is no dependency between the previous and next move, so there is no need to check the feasibility of the move.

f:id:simanman:20210506191317p:plain:w300

For example, if you are in the state shown in the image above

[[Hole 1, LEFT], [Hole 1, LEFT], [Hole 2, UP], [Hole 2, RIGHT], [Hole 1, DOWN], [Hole 2, RIGHT]]

It's like creating a sequence of operations like If you limit yourself to at least one move drop, this will give you a nearly optimal procedure.

However, when I dropped the 1 value block, I shifted it to the side instead of dropping it if it looked good, because shifting it to the side might increase the number of blocks for 2-move operations.

f:id:simanman:20210506212051p:plain:w250

Greedily select operations.

Although 1-move operations are given priority, there are cases where the latter is actually more efficient than "1-move operations twice to drop blocks 2 and 3", such as "2-move operations to drop block 9". For this reason, we chose the one that seemed best while also running the process of greedily deciding the operation in parallel.

It greedily chooses an operation, but if the best operation is more than three moves, it runs a search to see if it can manipulate the board so that it becomes a two-move operation.

f:id:simanman:20210506195519p:plain:w250

For example, in this situation, it would take three moves to drop the 7 block, but by sliding the block one move upper, it can be dropped in two moves.

f:id:simanman:20210506195802p:plain:w250

If you only look at this move, the result is the same: "drop the block of 7 in 3 moves" in both cases, but now that you can drop the block in 2 moves, the efficiency of dropping other blocks will also increase, resulting in a higher score.

f:id:simanman:20210506200531p:plain:w250

To check for the existence of such a board manipulation move, we check the orthogonal lines of "hole" and "block" and see if we can place a block in one square around the point.

f:id:simanman:20210506201025p:plain:w300

We have also included a process to remove the block of 1 to prevent "the block of 1 is blocking the 2-move operation".

f:id:simanman:20210506201441p:plain:w250

f:id:simanman:20210506201544p:plain:w250

Calculate the expected value of the value by getting the block

For overall optimization, the score including several moves ahead is more important than the maximum value at that timing, so I roughly calculated the value and added it to the evaluation.

f:id:simanman:20210506202542p:plain:w250

In the case of this image, processing it as [1, 9] would result in a higher score than processing it as [7, 1]. The evaluation was determined by the following code for the block that exists behind.

value += block_value / d 
d += random_value  # random_value is randomly set to a value between [1.0, 3.0] that is randomly determined before the search.

I adopted it because it scored better than [sum of blocks] / [number of moves required], but I honestly don't know why.

Treat operations as events.

The final sequence of operations will be handled as an array of events, with "drop the block into the hole" as the delimiter. So

1 2 S R
4 6 S U
8 6 S U
6 5 M R
6 6 S U
0 2 M D

Instead of a flat operation array like

[
  [1 2 S R],
  [4 6 S U],
  [
    8 6 S U,
    6 5 M R,
    6 6 S U,
    0 2 M D
  ]
]

It's like having an array of operations grouped in units of "drop blocks".

f:id:simanman:20210506205209p:plain:w400

Change the order of operation of events.

Move and manipulate the array of events obtained at the end as long as the overall operation is not broken. If the move raises the overall score, the replacement is adopted. (Hill Climbing)

The way to do this is to skip the application of the event to be replaced, and continue applying the event until the subsequent event fails, and then calculate the score of "if this event is inserted at this timing" each time and insert it at the timing with the highest value.

Try this both from the front and from the back, and if you can't update the score, you're done. (In the case of backward, it's like doing the reverse operation for each event from the board after all events have been applied.

This is the end of the process.

Event binding process

For certain events, the dependencies are so strong that they cannot be replaced properly, so we try to reduce the number of targets for the reordering process by grouping events to some extent.

f:id:simanman:20210506205626p:plain:w300

For example, in the image, the operation of inserting blocks in the order [7, 6, 9] is almost immutable, so we combined these events as one event instead of three. By grouping the events together, the variation in score that occurs in one replacement process becomes larger, and the range of improvement becomes larger, resulting in a higher score.

To determine whether or not to combine events, we used to judge whether or not the y-axis or x-axis of the coordinates of the first operation was equal. (If they are equal, the same decision is made.)

Repeat the process until the time limit is reached.

The rest of the process (procedure creation -> procedure replacement) will continue until the time limit is reached. Before execution, change the values of various parameters so that a different sequence of operations can be taken each time.

Impressions

I think the difference was in the greed to determine the operating procedure, and I realized once again that I lacked the ability to evaluate the board.

MM126 Slider 参加記

ブロックを効率よく穴に落とす問題でした

https://www.topcoder.com/challenges/82eb883d-85e9-442f-b0eb-91631d3d2a8f

方針

まずベースとなるブロックを落とす順番の操作配列を作成します。

操作配列の作成についてはいくつかステップを踏んでいます

1手落としの効率化

最初は 1手で落とせるブロックを列挙して落とす順番を焼きなましで求めます。

ブロックを動かす操作というのは前後の操作に影響を与えてしまうため、操作の順序を変更した際に現時点での操作が有効かどうかを毎回調べる必要が発生して、このコストがとても大きいので焼き鈍すのは難しいです。

ただ、1手落としの順番に関しては [穴の位置、ブロック取得する方向] の情報に落とし込めば前後の依存関係がなくなるため、実行可能性を調べる必要がなくなり十分高速な処理が可能になります。

f:id:simanman:20210506191317p:plain:w300

例えば上記の画像の状態であれば

[[穴1, 左], [穴1, 左], [穴2, 上], [穴2, 右], [穴1, 下], [穴2, 右]]

のような操作配列を作る感じです。少なくとも 1手落としに限定すればこれでほぼほぼ最適な手順が手に入ります。

ただ、 1のブロックを落とす際には横にずらすことで 2手操作のブロックが増える可能性があるので、良さそうであれば落とすのではなくずらしていました。

f:id:simanman:20210506212051p:plain:w250

貪欲に操作を決定

1手操作を優先的に実行はしますが、実際には「1手操作を 2回行って 1, 2 のブロックを落とす」より「2 手操作を行って 9 のブロックを落とす」のような後者のほうがスコア効率がよくなるケースが存在します。そのため、並行で貪欲に操作を決定する処理も走らせながら良さそうなものを選択しました。

貪欲に操作を決定しますが、一番良い操作が 3手以上の場合は 2手操作になるように盤面を操作できないかどうかを探索する処理を走らせます。

f:id:simanman:20210506195519p:plain:w250

例えばこの状況だと 3手で 7のブロックが落とせますが、1つ上のブロックを上にスライドさせることによって 2手で落とすことが可能となります。

f:id:simanman:20210506195802p:plain:w250

この手だけを見ればどっちも「3 手で 7のブロックを落とす」という結果は同じなのですが、2 手で落とせることになったことで他のブロックの落とす効率も上がるので結果的にスコアが出るようになります。

f:id:simanman:20210506200531p:plain:w250

このような盤面操作の手が存在するかどうかは「穴」と「ブロック」のラインが直交している部分を調べて、その周辺 1マスにブロックが置けるかどうかをチェックしています。

f:id:simanman:20210506201025p:plain:w300

他にも「1 のブロックが邪魔して 2手操作ができないパターン」の対策で 1のブロックを除去する処理を入れています。

f:id:simanman:20210506201441p:plain:w250

f:id:simanman:20210506201544p:plain:w250

ブロックを取得することによる値の期待値を計算

全体の最適化のためには、そのタイミングでの最大値より数手先を含めたスコアのほうが重要なので、ざっくりと値を計算して評価に加えていました。

f:id:simanman:20210506202542p:plain:w250

この画像のケースだと [7, 1] と処理するより [1, 9] と処理したほうがスコアが高くなる。 評価は後ろに存在するブロックに対して下記のコードで決定していました。

value += block_value / d 
d += random_value  # random_value は探索前にランダムに決定される値 [1.0, 3.0] の間でランダムに設定

[ブロックの総和] / [必要な手数] よりはスコアが出たので採用しましたが、正直理由はわかってないです。

操作をイベントとして扱う

最終的に得られた操作列は「ブロックを穴に落下させる」を区切りとしてイベントの配列して扱います。なので

1 2 S R
4 6 S U
8 6 S U
6 5 M R
6 6 S U
0 2 M D

のようなフラットな操作配列ではなくて

[
  [1 2 S R],
  [4 6 S U],
  [
    8 6 S U,
    6 5 M R,
    6 6 S U,
    0 2 M D
  ]
]

「ブロックを落とす」という単位でまとめられた操作配列を持つ感じになります。

f:id:simanman:20210506205209p:plain:w400

イベントの操作順番の変更

最後に得られたイベントの配列を全体の操作が壊れない範囲で移動操作します。移動によって全体のスコアが上がった場合はその入れ替えを採用します。(山登り)

方法としては入れ替え対象のイベントの適用をスキップして、その後のイベントの適用が失敗するまで適用を続けて、その都度「このタイミングでこのイベントを入れた場合」のスコアを計算して一番値が大きいタイミングの位置に挿入します。

これを前からと後ろからの両方試して、スコアの更新が出来なくなったら終了です。(後ろからの場合はすべてのイベントの適用後の盤面から各イベントの逆操作を行っていく感じです)

ここまでが一連の処理になります。

イベントの結合処理

特定のイベントに関しては依存関係が強くてまともに入れ替えができないので、ある程度イベントをまとめることで順番の変更処理の対象を減らすようにしています。

f:id:simanman:20210506205626p:plain:w300

例えば画像のような状態では [7, 6, 9] と順番よくブロックを入れるという操作はほぼほぼ不動なのでこれらは 3つイベントとしてではなく 1つのイベントとしてまとめています。イベントをまとめることによって 1回の入れ替え処理で発生するスコアの変動が大きくなり改善幅も大きくなって結果的にスコアが上昇しました。

イベントをまとめるかどうかについては「初手の操作の座標の y軸 or x軸 が等しいかどうか」で判定を行っていました。(等しいと同一判定)

時間制限まで処理を繰り返し行う。

あとはこの処理(手順作成 -> 手順入れ替え)を時間制限が来るまで回し続けます。実行前は様々なパラメータの値を変更して毎回違う操作列が取れるようにします。

感想

貪欲に操作手順を決めるところで差がついてしまったと思うので、盤面評価力が足りてないなと改めて痛感しました。