SRM291 に挑戦.

2006 TopCoder Open - Algorithm Competition が来週の 2 月 28 日から始まります.今日は,それに向けて最後の SRM になります.

Snowflakes (code)

250 点問題. 折り紙で雪の結晶をつくりましょう.

SRM291 Snowflakes From TopCoder

このように 8 等分に折った折り紙に対して,

SRM291 Snowflakes From TopCoder

穴をあけてから,もとの大きさに広げると,あら不思議.雪の結晶のように,対称な,きれいな形ができますねー.あ,そう.って感じですが,,,

問題は,以下のような,8 等分に折った折り紙の穴のパターンが与えられたとき,

{"*",
 "..",
 ".*.",
 ".**.",
 ".*.**"}

広げた形のパターンを求めてください.という問題です. 例の場合は,答えはこのようになります.

{"**.*..*.**",
 "*.**..**.*",
 ".*.*..*.*.",
 "***....***",
 "....**....",
 "....**....",
 "***....***",
 ".*.*..*.*.",
 "*.**..**.*",
 "**.*..*.**" }

悩むところは特にないですが,いかに時間をかけないでサクサクすませられるか勝負です.私は,14 分もかかってしまいましたが,早い人は,3 分かからずにすませています..

CrazySwitches (code)

500 点問題. 素直に BFS でいけました.

StickedWords

1000 点問題. Level1,Level2 問題を終えた時点で,30 分ほど時間がありましたので,1000 点問題にチャレンジ.

単語(Word)がいくつか与えられます.

{"salad", "sandwich", "hamburger", "rings"}

これらの単語の最初の文字と最後の文字をつなげて,文字列をつくっていきます.

"hamburgeringsandwichamburger..."

「しりとり」ですね.こうしてできる文字列のうち,長さが (len) 以上のもので,辞書的に一番最初にくる文字列を返しなさい.という問題です.たとえば,入力が

{"abd", "dgga", "abdg", "gga", "gg", "gaader"}
len: 22

ならば,

"abdggabdggabdggabdgaader"

が答えになります.

ちょっと考え,「DP でいける!」と思い,コーディング開始. 1000 点問題をはじめて解けるか?! と思ったのですが,時間が足りませんでした.. SRM 後,悔しいので完成させました.

public class StickedWords {

  public String constructLine(String[] dict, int len) {

      String[][] dp = new String[len+51][26];
      for (int i = 1; i < dp.length; i++) {
          for (int j = 0; j < 26; j++) {
              for (String word : dict) {
                  if (word.charAt(word.length()-1) - 'a' != j) continue;
                  if (i == word.length()) {
                      if (dp[i][j] == null || word.compareTo(dp[i][j]) < 0) {
                          dp[i][j] = word;
                      }
                      continue;
                  }
                  int pre = i - (word.length() - 1);
                  if (pre <= 0) continue;
                  int first = word.charAt(0) - 'a';
                  if (dp[pre][first] == null) continue;
                  String next = dp[pre][first] + word.substring(1);
                  if (dp[i][j] == null || next.compareTo(dp[i][j]) < 0) {
                      dp[i][j] = next;
                  }
              }
          }
      }
      String res = null;
      for (int i = len; i < len+51; i++) {
          for (int j = 0; j < 26; j++) {
              if (dp[i][j] == null) continue;
              if (res == null || dp[i][j].compareTo(res) < 0) {
                  res = dp[i][j];
              }
          }
      }
      return (res == null) ? "" : res;
  }
}

もうちょっとだったのになーと思っていたけど,実は,ぜんぜんもうちょっとではありませんでした.落とし穴がまっていました.. 最悪のケース (len=2500)では,

java.lang.OutOfMemoryError...

を引き起こしてしまいます. TopCoder では時間(2 秒以内)という制限だけではなく,使用メモリの制限 (64Mbyte)もありました. 最悪のケース (len=2500)では,最終的にはメモリ使用量は,

2500 x (2500 / 2) x 26 = 81250000

ですから,80MByte を超えますね...これはトリッキーだ..普通,気づかないよな.. 1000 点問題にしてはとっかかりやすいと思ったけど,こういう側面があったとは..こういうのは,問題作成者が意図的にやってるんですね.

ひとつあたりの単語の長さは最大 50 なので,DP をしつつ,最近の 50 ステップ分だけ,覚えるようにして,いらなくなったものには,null をいれると.

}
if (i - 51 >= 0) {
    dp[i-51] = null;
}

これで,なんとか System Test に通るようになります.

結果

System Test の結果です. ( Room Statistics )

Room Statistics

レーティングは 1626 -> 1730 と 100 ほど上昇しました.いよいよ来週は,2006 TopCoder Open の予選です.

今日の教訓