SRM 423 Level1 TheTower

参加できなかった前回のTopCoder SRM、遅ればせながらPractice Roomでやりました。噂ではかなり難しかったらしい。


時間を75分計ってやってみたところ、Level1(300点問題)は35分くらいでsubmitできました。システムテストも通って152pt。これは確かにいつものLevel1よりかなり難しい。普段は単純にさっさと書くだけの問題のことが多いけれど、今回はアルゴリズムをある程度自分でひらめかないといけない。難しくもいい問題だったと思います。
Level2(500点問題)は途中まで。こっちもけっこう難しめでした。あとで他の人の解答を見る。


以下Level1の解説をつらつらと。

問題

格子点上にあるコマがN個(N<=50)与えられる。コマを格子に沿って移動させて、どこか一点に決められた数のコマを集めたい。どの点に集めてもいいし、どのコマを使ってもいい。集める数が1〜Nのそれぞれに対して、最小の移動距離を求めよ。

条件:各コマのx座標{1, 2, 4, 9}、各コマのy座標{1, 1, 1, 1}
解答:{0, 1, 3, 10}


解説:2個集めるときは、(1,1)を(2,1)へ移動させるだけなので移動距離1。
3個集めるときは、(1,1)を(2,1)へ、(4,1)も(2,1)へ移動させて合計の移動距離3。
4個集めるときは、全部を(2,1)へ移動させて合計の移動距離10。

ポイント
移動距離が最小となる点のx座標は、どれかのコマの初期x座標と同じところのみを考えればよい。y座標も同じ。つまり、N×N個の点だけを調べれば十分。
理由
格子上の移動なので、xとyは別々に考えて良い。のでx座標だけ考える。
(X1, X2, ... , Xn)を座標Xに集めるとする。ここで一般性を失わずに(Xi <= Xi+1)としておく。
このときの移動距離は Σ|X - Xi|。これは[Xi, Xi+1]間ではただのXの一次関数で、図に書くと各Xiで傾きが変わる折れ線グラフになる。なので、最小値を取る位置の候補としてはXiだけを考えればよい。


N×N個の候補点それぞれに対して、以下を行います。

  • 各コマとの距離を計算する
  • 距離の小さいものから順に足していく
  • これで、その点でコマを1個、2個、…、N個集めるときの最小移動距離が分かる

この最小移動距離を候補点ごとに記憶しておきます。都合、N×N×Nの配列になる。
配列の名前をaryとすると、ary[i][j][k]が表すのは、「点 (コマiの初期x座標, コマjの初期y座標) に、k個コマを集めるときの最小移動距離」です。


あとは、コマを集める数が1個、2個、…、N個のそれぞれに対して、記憶した中から最小の移動距離のものを探せばOK。


少ないコード量で済んだので貼っときます。

import java.util.Arrays;

public class TheTower {

  public int[] count(int[] x, int[] y) {
    int N = x.length;
    int[][][] s = new int[N][N][N]; // これに候補点ごとの最短移動距離を記憶する
    for (int i = 0; i < N; i++) {
      for (int j = 0; j < N; j++) {
        int[] a = new int[N];
        for (int k = 0; k < N; k++) {
          a[k] = Math.abs(x[k] - x[i]) + Math.abs(y[k] - y[j]); // 各コマとの距離
        }
        Arrays.sort(a);
        int ac = 0;
        for (int k = 0; k < N; k++) {
          ac += a[k]; // 近いものから順に足していく
          s[i][j][k] = ac;
        }
      }
    }
    int[] ret = new int[N];
    for (int n = 0; n < N; n++) {
      int min = Integer.MAX_VALUE;
      for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
          // 記憶した中から最短のものを探す
          min = Math.min(min, s[i][j][n]);
        }
      }
      ret[n] = min;
    }
    return ret;
  }

}


【追記】nitoyonさんの解説のほうがわかりやすそう。