2013年12月17日火曜日

main() のすり替え in C++

[2013/12/18] ptr_umain 経由の呼び出しで引数を付けていなかった点を修正。

ある種のライブラリの場合、main() の呼び出し前に処理をしておきたい場合があります。ものすごく真っ当な方法で言うならスタートアップルーチンを書くべきところではありますが、その場合、ライブラリを使う側でもオプション指定が必要になったりしてちょっと面倒になります。こういう時に、ライブラリ側で main() を定義してしまいユーザーコード側では

// main.h
#define main _umain
#ifdef __cplusplus
extern "C" int _umain(int argc, char **argv);
#else
extern int _umain(); // No specification for parameters in C
#endif

// main.c
#include "main.h"
int main(int argc, char **argv) { /* ... */ } // replaced with _umain

のようにヘッダを読み込ませることによって main() をすり替えてしまい、ライブラリ側の main() からすり替えた _umain() を呼び出す、といったことが行われる場合があります。例えば SDL (http://www.libsdl.org/) なんかはこれを使っているようです。

が、問題が一つ。main() は引数が固定されていません。実用的には、以下の3種類くらいあると考えてよいでしょう。

int main(void);
int main(int argc, char **argv);
int main(int argc, char **argv, char **envp);

C の場合、これでもそんなに問題になりません。すり替え用マクロが有効であるとして、上記コードのように extern int _umain(); としておけば、C 言語では引数に関しては指定がない扱いであり、かつ可変引数関数を実現する C の呼び出し規約上、上記いずれの定義であっても _umain(argc, argc, envp); と呼び出してしまえば、普通の処理系ならまず問題なく動作するはずです。

が、C++ の場合、プロトタイプ宣言が必須であり、また、関数の型チェックが必ず行われます。つまり main() がどの形式で定義されていたかによって呼び分ける必要が出てくるわけです。前述の SDL では main() の型を int main(int, char**) に限定することで回避しているようです。最初から特定ライブラリを使う前提であればそれでもいいのですが、今作成中のライブラリ(UTF-8 Win32 API)は後付けの形での利用を想定しているので限定はちょっとつらいです。

要はユーザーがどの形式で main() を定義したか判別する方法があればいいわけです。そこで(処理系依存ですが) GCC の weak symbol と alias を使用してみました。

int _umain_stub()
{
 return 0;
}

// for C
extern "C" int _umain(...) __attribute__((weak, alias("_Z11_umain_stubv")));
// for C++
extern int _umain() __attribute__((weak, alias("_Z11_umain_stubv")));
extern int _umain(int argc) __attribute__((weak, alias("_Z11_umain_stubv")));
extern int _umain(int argc, char ** argv) __attribute__((weak, alias("_Z11_umain_stubv")));
extern int _umain(int argc, char ** argv, char **envp) __attribute__((weak, alias("_Z11_umain_stubv")));

static int(*ptr_umain)(...)                   = static_cast<int(*)(...)>(_umain);
static int(*ptr_umain0)()                     = static_cast<int(*)()>(_umain);
static int(*ptr_umain1)(int)                  = static_cast<int(*)(int)>(_umain);
static int(*ptr_umain2)(int, char**)          = static_cast<int(*)(int, char**)>(_umain);
static int(*ptr_umain3)(int, char**, char**)  = static_cast<int(*)(int, char**, char**)>(_umain);

template<typename T>
static inline bool is_target(T t)
{
 return t != reinterpret_cast<T>(_umain_stub);
}

int main(int argc, char **argv, char **envp)
{
 if(is_target(ptr_umain))  return ptr_umain(argc, argv, envp);
 if(is_target(ptr_umain0)) return ptr_umain0();
 if(is_target(ptr_umain1)) return ptr_umain1(argc);
 if(is_target(ptr_umain2)) return ptr_umain2(argc, argv);
 if(is_target(ptr_umain3)) return ptr_umain3(argc, argv, envp);
 return -1; // Not reached if main() is user-defined
}

通常、同名の関数定義がある場合、多重定義エラーになります。weak symbol を使うと、他に同名の定義がない場合だけ有効になる定義を作ることができます。デフォルト実装の定義を作っておいて、場合によってはより高速な実装に差し替えることも可能、みたいな使い方が典型的なユースケースです。alias はある関数を別の関数の別名として定義できる機能です。上記コードでは C/C++ の _umain() 系を全て _umain_stub(void) の別名かつ weak symbol としています。ユーザーがある形式の main() を定義した場合、マクロで _umain() にすり替えられていずれかの weak symbol が上書きされます。結果として _umain_stub(void) と値(アドレス)が変わるため、それを is_target() で判別して呼び分けているわけです。

で、話が済んでいれば幸せだったのですが、なぜか StrawberryPerl 5.18.1.1 32bit 中の GCC を使って、↑ main() のあるソースファイルで #include <iostream> や #include <cstdio> すると関数ポインタの値がずれます。Cygwin の i686-w64-ming32-g++ だと問題ありません。両方とも GCC-4.7.3 なんですが。例えば実際のアドレスが、
__Z7_umain_v (ユーザー定義の _umain(void)) -> 0x401560
__Z11_umain_stubv (_umain_stub(void)) -> 0x40195e
である場合に、バイナリ中で参照されるアドレスがそれぞれ、0x40117e, 0x40157c だったりします。両方とも 0x3e2 だけずれています。バグなのか設定がおかしいのかちょっと分かりませんがとりあえず以下のようなコードで無理やり補正すると一応動作はします。ユーザー定義の _umain() と _umain_stub(void) の 2 種類があるだけで、かつ、ユーザー定義は 1 つだけ存在するはず、という前提で多数派が実際には _umain_stub(void) であるとみなしてその差を補正しています。

template<typename T>
static inline unsigned long get_offset(T t)
{
 return reinterpret_cast<unsigned long>(_umain_stub) - reinterpret_cast<unsigned long>(t);
}

template<typename T>
static inline void add_offset(T& t, unsigned long offset)
{
 t = reinterpret_cast<T>(reinterpret_cast<unsigned long>(t) + offset);
}

void fix_fptr()
{
 std::map<unsigned long, int> counter;
 ++counter[get_offset(ptr_umain)];
 ++counter[get_offset(ptr_umain0)];
 ++counter[get_offset(ptr_umain1)];
 ++counter[get_offset(ptr_umain2)];
 ++counter[get_offset(ptr_umain3)];
// assert(counter.size() == 2);
 unsigned long offset = counter.begin()->second > (++(counter.begin()))->second ? counter.begin()->first : (++(counter.begin()))->first;
 add_offset(ptr_umain, offset);
 add_offset(ptr_umain0, offset);
 add_offset(ptr_umain1, offset);
 add_offset(ptr_umain2, offset);
 add_offset(ptr_umain3, offset);
}

ちょっとどうだかなという感じではあるので、とりあえず、#include <iostream> や #include <cstdio> を外して様子見な感じですが謎な状態です。

2013年11月22日金曜日

\L と \U と Perl

Perl スクリプトに文字列を渡すのに

count -t '\t'

のような感じでエスケープを使いたいけれど、Perl コアでそのものの機能は提供されていないということで String-Unescape というモジュールを作成中です。もちろん eval 使えば済むのは分かってるんですけど、文字列 eval はなるたけ避けたいということで。その中でぶつかった(自分的には)驚きの Perl 仕様について書いてみます。

ダブルクウォート内で \Q と \E で囲った範囲の英数アンダーバー以外をエスケープできる(quotemeta がかかる)というのは Perl 使いではそれなりに知られた事実だと思います。

say "\Q[|]\E";
# \[\|\+\]

同様に範囲で効く変換として \L, \U も存在は知っているという方も多いでしょう(最近は \F と言うのもあります)。で、これらは重ねることができると perldoc perlop には書いてあります。

\L, \U, \F, and \Q can stack, in which case you need one \E for each. For example:
 say"This \Qquoting \ubusiness \Uhere isn't quite\E done yet,\E is it?";
# This quoting\ Business\ HERE\ ISN\'T\ QUITE\ done\ yet\, is it?

さて、では問題です。以下はどのように出力されるでしょうか。

say "\Q[A\U[a\L[A\E[a\E[A\E";

正解は \[A\[A\[a\[a[A です。この時点で「は?」って感じですが、続いて第 2 問。

say "\U[a\Q[a\L[A\E[a\E[a\E";

正解は [A\[A[a[a[a です。もう訳がわからないという感じですが、真っ当に追っかける気を無くすコードを感覚で読み取ったところ、\U 中の \L や \L 中の \U があると、その前の \U, \L まで(途中に \Q とかがあればそれも)無効になった後で、新しく \L, \U が有効になるようです。\L や \U が一回しか有効にならないように \E が適宜補われると考えると分かりやすいかもしれません。

say "\Q[A\U[a\L[A\E[a\E[A\E";
say "\Q[A\U[a\E\L[A\E[a\E[A\E"; # 直前の \U が無効
# \[A\[A\[a\[a[A
# \[A\[A\[a\[a[A
say "\U[a\Q[a\L[A\E[a\E[a\E";
say "\U[a\Q[a\E\E\L[A\E[a\E[a\E"; # \U\Q が無効
# [A\[A[a[a[a
# [A\[A[a[a[a

\L...\U...\E...\E ってやっても外側の \L しか効かないから無駄じゃねーか、という意図であろうというのは推測できますが、個人的には「いや、書かれたとおりに実行しろよ」と言いたくなる挙動です。

もう一つ、エスケープ関連でお節介じゃないの?という機能として、\L\u と \U\l をそれぞれ \u\L と \l\U に置き換えるというものがあります。

say "\L\uaAA\E \U\lAaa\E";
# Aaa aAA

\u してから \L したら結局 \L だけじゃねーかってのは分かりますが、「勝手に書き換えるな、警告出せ」と思うわけです。

ちょっと気を利かせたつもりで全体として訳が分からなくなる、というのはある意味とても Perl らしいという気もしますが。元々 String-Unescape は Perl の処理と一致させることを目指そうと思っていたわけですが、この 2 つの挙動(特に前者)は無理して再現する必要はないだろうということで実装しないつもりでいます。

2013年9月26日木曜日

YAPC::Asia Tokyo 2013 参加メモ(二日目)

前稿に引き続き、聴講したトークの雑感を箇条書き的に。

Mojoliciousでつくる!Webアプリ入門 by Yusuke Wada

  • WAF 自作も楽しいけど、そこは本題じゃないんだからとりあえず WAF 使っとくのが良い、と。
  • ライブコーディングでさくっと作られてたのですごい簡単感が伝わってたと思います。
  • 「分けることで分かる」「少しづつやる(ことで俺スゲー感を持続)」俺スゲー感はモチベーション持続に有用ですよ、本当。新しいことやると楽しいんですよね。
  • CCF は WAF 使ってないんですが、特に model がきちっと分かれてないところは次の大改修前には手直ししたいという気になりました。

Programming AWS with Perl by Yasuhiro Horiuchi

  • with Perl 要素少なめ。
  • Python 製 AWS CLI が提供されてるので、Perl での自動処理用にはそれに対する wrapper AWS::CLIWrapper を使えば良いのではないかと、という感じですかね。
  • CLI と API 直叩きモジュールとどちらが良いという質問がありましたが、自動処理用には CLI 経由でいいでしょうけど、アプリ的にはモジュール使用なんじゃないですかね。

What's new in Carton & cpanm by Tatsuhiko Miyagawa

  • もちろん cpanm は使ってますけど Carton は使ってないんですよね。個人の趣味の領域だとそこまで必要性を感じないというか。
  • git URL 可になったのは patch 版指定とかできて嬉しい感じではあるんですけど、Dist::Zilla で github に置くと普通はインストール可にならないんで、その辺どうするのがいいのかな、と思ったりします。

GitHubでつくる、たのしい開発現場 by Hiroki OHTSUKA

  • どっちかっていうと精神よりというか、本来そういうことの方が重要だとは思うんですが、やっぱり具体的なツールとか Tips の方が分かりやすいんですよね。

可視化のためのPerl by Muddy Dixon

  • 可視化には Explanatory (ストーリー説明) と Exploratory (ストーリー探索) の 2 種類がある。
  • マイニング技術者はドメイン知識が少ない v.s. ドメイン専門家はマイニングの知識が少ないので可視化してドメイン専門家に見てもらおう、と。
  • DataCube の開発止まってるじゃんということで、Data::Cube をリリースされたとのことですが、「僕はこの手の処理はRでやります」という衝撃の結末からは、どうせこのモジュールも開発止まるんでしょ?、感が激しくします。

本当にあったレガシーな話 by Daisuke Maki

  • livedoor blog のモダン化について。モダン Perl 入門 増補改訂版 に書かれるそうです。題名は増補改訂版になっていますが 80% 変わっているとのこと。
  • 発表的にはとりあえず mod_perl dis だったと思っておけば大体合ってるんじゃないでしょうか。
  • とにかくログをとることがまず最初。設定切り替えには use constant による定数たたみ込み最適化の利用をすると良い。
  • 他色々あったんですが、$0 変えると ps で見たときに分かるというのは Tips は面白かったです。

スマフォアプリ開発を支える認証認可アーキテクチャ by Rieko Suzuki

  • 執筆記事の著者写真の時点でなんかもう色々と違う気がしましたが「糞アプリ」発言で逆に安心しました。

PhantomJSによる多岐にわたる広告枠の確実な表示テスト by Yusuke Yanbe

  • Selenium::Remote::Driver を使えば headless ブラウザである PhantomJS を操作できるのでそれを使ってテストするという話。

フルテストも50msで終わらせたい 〜 FreakOutの取り組み 〜

  • 最初にさすがに 50ms は無理ですとの潔いお言葉。
  • 一日目の soh335 さんの発表と結構被っていたとのこと。テスト用ユーティリティモジュールも用意されているそうです。
  • 実行順序に依存するテストを避けるために、テスト順序のシャッフルがおすすめとのこと。
  • File::Find, List::MoreUtils, Parallel::ForkManager, Net::OpenSSH, TAP::Harness といった CPAN モジュールを組み合わせて分散実行することで 2,000 秒 over → 180 秒に短縮。
  • CPAN モジュールの組み合わせというのがとても Perl らしくていいですね。
  • 「高速なテストは開発文化を変える」手元でやるより push した方が速いのでとりあえず push する文化に→コードが早期共有されてレビューも進み品質も向上。

LT

とりあえず分かる範囲でメモ。名前が分からない方とかもし抜けてたらすみません。

  • テストカバレッジについて http://coveralls.io/ というサービスがある。
  • DBIx::* の新しいモジュール。
  • makamaka さんの同人活動レポート。
  • HTTP から SMTP 送信ができる Haineko について。
  • Perl 入学式と近所の Perl Mongers のおかげで初心者でも「つぶやいてないでコード書け」というサービスができました!という話。いい LT だったと思います。
  • papix さんの Perl 入学式活動レポート。
  • kamipo さんの mysql-build の話。
  • もっとネタモジュールを!ということで netacpan.org できました。
  • kamadango さんのマイクロコミュニケーションの話。
  • tokuhirom さんの Test::Power っていうか HTTP::Body::Builder 作ったった。
  • mruby_nginx_module について。
  • pjf さんの method add_datapoint( Str: $goal!, Int:$timestamp) みたいに書ける Method::Signatures の紹介。
  • takesako さんの Perl クイズ。近藤嘉雪先生が優勝してました。チート級。識別子が 251 文字以下とかそんな制限あったんですね……

Keynote by Tomohiro Ikebe

  • 「Management and Perl culture」というタイトル。
  • マネージャーで画像検索するとリア充 マネージャーって管理するというより支える方じゃないの?
  • できるエンジニアにできるからマネージャーやれ、は最悪。とはいえチーム内のリーダーシップは必要。
  • 社内に対する広報(エンジニアの仕事が意味のある仕事だってことを伝える)ことも大事。
  • ……そんなに年が変わらないということにちょっとショックを受けました。

YAPC::Asia Tokyo 2013 クロージング by Daisuke Maki

  • 牧さん、櫛井さんのツートップによる YAPC::Asia はこれで終了、とのこと。後を継ぐ人が居ないとこれで終わり、ということなんでしょうか?
  • のべ参加者 1,000 人オーバー。うーむ、すごい。

まとめと余談

  • 今年の YAPC は Perl の話題が多いな、とかいう tweet を見ましたが、確かに Perl 寄りな感じで個人的には嬉しかったです。
  • 本当は今年発表できたらなーと朧気に思っていたのですが(Dist::Zilla の話とか)、Minilla と Milla のトークが応募されてるし、しかも Ricardo SIGNES ご本人来てるしってんで無理ゲーでしたね。LT できたらなーというネタは今年は余裕で間に合わなかったので来年に LT できたらなーと思っています。
  • 今回新しいノート PC (VAIO Pro 13)で参加しました。今まで一台のノート PC をどのシーンでも使い回す感じだったので 2kg 前半まで選択肢に入れていたのですが、重量というよりもバッテリーの持ちからこの手のイベントではちょっときついな、ということでモバイル用として購入。シートバッテリがなかったとしてもイベント中はもってそうでした。重量はあんまり気にしてなかったんですが実際に使ってみると、部屋の移動のときに横に抱えるのも楽だし AC アダプタもなくてそのまま運べるしで良かったと思います。

YAPC::Asia Tokyo 2013 参加メモ(一日目)

遅ればせながら、YAPC::Asia Tokyo 2013 参加メモ。

目が覚めたらいきなり当初乗るつもりだった電車の時間でしたが、最初の説明にわずかに遅刻する程度の時間で着。いきなり席が埋まってる感じでちょっとびびりました。メインの藤原洋記念ホール 1階席は後ろからの入り口が2ヶ所あるのですが、一度どっちかの入り口から入ってしまうと途中で別の通路側まで行くのは狭い席の間を移動するしかなく(ステージ直前では移動できたかも)手前通路側が埋まりがちになっていた気がします。 以下、聴講したトークの雑感を箇条書き的に。

Postcards from the Edge: The State of Perl 5 Development by Ricardo SIGNE

  • Dist::Zilla でも大変お世話になっている RJBS こと Ricardo SIGNES 氏の Perl5 の今後についての発表。
  • とりあえず pumpkin というのは Perl 5 のリリースマネージャ(というのが正確か分かりませんが)的な立ち位置の人だ、というのは以前の参加の時に聞いているので知っているのですが、何言ってるのかさっぱり分からない参加者の人とか居たんじゃないかな、というのは少し気になりました。
  • 「Perl5 は Perl5 であり続ける(意訳)」という言葉は印象的でした。
  • postfix dereferencing @{$hoge}$hoge->@* と書けるというのは確かにちょっとキモいですが Perl らしいキモさな気がします。実際 $hoge->[3]->{key} とか書いた後、あぁ array dereference しなきゃってんで、カーソル戻して @{ } で囲うとかいうのは超頻出パターンなので便利にはなるはず。
  • 本体開発側では互換性によってはじかれるパッチとか改良とかは少ないかもしれないですが、利用者側で互換性のために使用しない機能、とかは割とある気がします。モジュール書いててもより低いバージョンをサポートするためにより面倒な方法に書き直したりすることもありますし。強制アップデート的なことができれば楽かもしれませんが。

Perlのモジュールを公開するときに気をつけておいた方がよい**個のこと by Kenichi Ishigaki

  • 基本的に CPANTS の Kwalitee の説明という感じで、Test::Kwalitee::Extra とか書いてるくらいなので大体のところは既知でした。
  • バージョンの形式(v0.1.5 だとか 0.1.5 だとか 0.001005 だとか)について聞いてみましたが、「特殊なことしないのであれば 0.01 とか使うのが楽」とのことでした。なんかコンセンサス的なものが欲しいです……

Perl and Riak - 分散データストア Riak を Perl から "爆速" で使うために - by Tatsuro Hisamori

  • FreakOut さん所属。(前からそうで気付いてなかっただけかもしれないですが) YAPC での存在感が大きくなってる気がします。
  • 内容についてはどうこう言えるほど知らない分野なので割愛。
  • 「継続的に運用していくシステムにおいては"テストができる"ことはマスト要件」これ本当大事。

Inside amon2-livedoor-setup.pl with web application development 2013 by Kazuhiro Osawa

  • Team Geek 買いました。
  • 溺愛の初期設定スクリプトだと社内事情が考慮されてないので、各社でひな形設定スクリプトを作っておくとよいよ、という話。
  • コピペを排除するためのひな形スクリプトがコピペ対象に、というのはものすごく有りそうな話。
  • 「開発から本運用に必要なすべてを盛り込む」これは確かに大事だけど抜けやすそうな気がします。

SPDY、HTTP/2.0の使い方 by takesako

  • 使いどころ、というところまでは達してなくて今使うならどうするか、くらいでしょうか。
  • HTTPS 接続を強制する HSTS (HTTP Strict Transport Secruity) は初めて聞きました。
  • Outbound Port 80 Blocking は笑ってしまいましたが、でもそれでもいい気がしますね。企業等のフィルタリングが辛いかもしれませんが。MITM 的に中継する HTTPS フィルタ proxy とかは SSL によるセキュリティの根本的なコンセプトを損なう最低の存在だと思っているので許容不可。

Types and Perl Language by Masahiro Honma

  • 人全然居ないんじゃないの?と思ったら座れませんでした。
  • トラブルがあってデモができなかったり、本論に入る前のところで終わったりした感じで、もっとちゃんと聞きたかったですね。
  • ひょっとしたらトラブルが無かったらそうだったのかもしれませんがバックグラウンドの説明を廃して Typed Perl の実例側から話す、という流れもありだったんじゃないかと思います。

モダンPerlリファクタリング by Naoya Ito

  • Perl 徹底攻略 掲載ネタです。毎回全く新しいネタを話すというのはとても難しいことだとは思うのですが、既に書籍を持っている状態で聞くと割と悲しい気分になったりするので、発表→書籍出版の順番になるようにしてもらえると宣伝にもなって双方がハッピーかもしれません。いや、トーク内容にも書いてあるし確認しておけば良かったんですけど。
  • とにかくまずはテストを作ること、細かいテストをちまちま書いていくという意味ではなく、とにかく大外の I/F 切ってテストを書いて、あとはテストとコード修正を並行していく、という感じでしょうか。
  • Test::Base で DATA セクションに置くとかまではやらないですけど、テストケースをデータにまとめておくってのは良くやりますね。

perl な web application のためのテスト情報 by soh335

  • テストを書いてもらうために、テスト用の共通モジュール t/Util.pm を作ってテストを書く障壁を下げている、とのこと。
  • Web application に限らないですし、各種 Test モジュールの簡単な紹介もあるのでさらっとスライド見ておくといいんじゃないでしょうか。
  • コードだけじゃなく、ファイル命名規則だとか用語チェックだとかもテストすればよい、というのは確かにその通りでわざわざ別の枠組み使わなくても TAP で合わせてしまえばいいですね。

はてなのサーバ管理ツールの話 by Yuuki Tsubouchi

  • ものすごくざっくりまとめると、運用系のツールは各種あって組み合わせの柔軟度は持っていたいけど設定が分散するので中央のコントローラーは内製といった感じでしょうか。
  • 知見や経験がほとんどないところなんでなんとも言えないですが一通り紹介というよりはポイントに絞って話してもらったほうが良かったかもしれません。

LT

題目と発表者リストがあるかと思ったらスケジュールの所にないですね……。とりあえず分かる範囲でメモ。名前が分からない方すみません。

  • kazuho さんの prove の発表。prove は汎用 taskrunner である、と。(今後使うか分からない)拡張子限定の解除とか Tips 満載でした。
  • mobile factory の人。えーと何でしたっけ?モテる台詞でしたっけ?今年のネタ枠。
  • Tachikoma の話。依存ライブラリ指定を更新して commit して pullreq 出してくれる。n-click から 1-click だと商売になって、1-click から 0-click だと革命という台詞が印象に残りました。
  • yappo さんの HTTP::Builder::Body が欲しい、という話。
  • hirose31 さんの inspect running perl process という話。strace とかだと syscall しか見られないし gdb 系だと特定の環境に依存するしということで、inspect-perl-proc を作成。例えば perl プロセスが詰まってるときにどこの関数で詰まってるかとかが分かる。いや、これすごいんじゃないでしょうか。
  • yusukebe さんの YAPC::NA 行ってきた話。どこから来たの?で話ができるし、こちらから話を切り出せば聞いてくれるよ、とのこと。
  • eikichi さんの YAPC::Tohoku やりたい、という話
  • comewalk さんの OSS に対する貢献の話。別にコード書くだけが貢献じゃない、と。規模によるでしょうが、自分のような弱小個人だったりすると単に反応があるだけでも嬉しいですしバグレポだけでも「使ってくれてる人居たんだ(泣)」という気分になります。
  • bayashi さんの module 紹介。アップロードしたところで自分もすぐに使わなくなるかもしれないですが、ローカルにおいてるだけだと絶対に今の自分にしか使えないですが、上げれば誰かが使えるかもしれないですし、テストやドキュメントを書くので将来の自分にとっても役に立ったりするかもしれません。Milla, Minilla, Dist::Zilla とか使うと簡単にモジュールをアップロードできます。おすすめ。
  • gfx さんの Power Assert と Emscripten による perl.js の話。
  • mizuki_r さんの p5-Spica (Web service wrapper) の話。
  • Songmu さんの Riji (日記) の話。いや、中国語分からないっす。

以下次稿

2013年5月4日土曜日

Google Code Jam 2013 Round 1A

遅ればせながら。

結果

ooo-o- 46pts の rank 546 ということで無事 Round1 通過です。どうも TCO より GCJ の方が相性が良い気がします。とりあえず PATH の設定がおかしくて exe 実行時に DLL がないって怒られたり、ブラウザのデフォルトダウンロードパスが gcj2012 になってたりスタートの所で結構いらいらしてましたが通って良かったです。Problem B を short 限定解で割り切ったのが正解だった気がします。テンプレートは前回参照です。なお、Problem Analysis 未読状態です。

Problem A. Bullseye

基本二分探索でやるだけ、ですが単純にやると 64 ビットでオーバーフローするようになってるのが味噌でしょうね。式的には n 個目の円を描くのに必要なインクの量は (r+2n-1)2 - (r+2n-2)2 = 2r+4n-3 なので c 個目の円まで描くのに必要なインクの量は Σ(2r+4n-3) = 2cr+2c(c+1)-3c = c(2r+2c-1) となります。t ≧ c(2r+2c-1) を満たす最大の c を求めるということで二分探索すればいいわけですが 64 ビットだとオーバーフローしてしまうので、t/c ≧ 2r+2c-1 で計算しています。2r+2c-1 が整数になるので t/c が整数除算(切り捨て)でも > だった場合が = になるだけなので問題有りません。

多倍長をサポートしている言語を使う、GCJ は外部ライブラリの使用も可能なので多倍長ライブラリを使うといった対処もあったのですが面倒だったのでオーソドックスに先に割り算しておく、で対処しました。が、gcc は 4.6 から __int128 をサポートしているようなのでそれを使うのが一番簡単だったかもしれません。

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  ULL r, t;
  cin >> r >> t;

  ULL l_ = 1ULL, r_ = 2000000000ULL;
  REP(i, 50) {
   ULL tt = (l_+r_)/2;
   if(t/tt >= 2*r+2*tt-1) {
    l_ = tt;
   } else {
    r_ = tt;
   }
  }

  cout << "Case #" << casenum+1 << ": " << l_ << endl;
 }

 return 0;
}

Problem B. Manage your Energy

しばらく考えていましたが Large 解は見えなさそうだったので Short 限定解として DP で解きました。大抵 DP の時は memoize 的に書く場合が多いのですが、超正統派な DP なのでループで書いてます。今回 Large 解を出せていませんが、一旦 Short 解を作って通しておいて実績を作り、Large 解を作った時の検証に使う、というのはそれなりに有効な戦略だと思うので 1 つの解で Short/Large 両方というのにはそんなに拘わらなくていいと思っています。

終了後のコメント見ると greedy と言っている人がいました。時間中に考えていたことと合わせると、全 Energy は E+(N-1)R で、最大係数に最長時間を割り当てて、それより後では後ろの係数が大きいところは Energy を消費せずに積む、最大係数より前では前の係数が大きいところでより消費させてその場では Energy を消費させない、みたいにずらしていくことでいける、かも?

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  UI E, R, N; cin >> E >> R >> N;
  vector<UI> v(N); for(auto &i : v) { cin >> i; }
  vector<vector<UI>> t(N, vector<UI>(E+1, 0));
  REP(i, t[N-1].size()) {
   t[N-1][i] = i * v[N-1];
  }
  REP(j_, N-1) {
   UI j = N-2-j_;
   REP(i, t[j].size()) {
    UI temp = 0;
    REP(k, i+1) {
     UI kk = i-k+R;
     if(kk > E) kk = E;
     temp = max(temp, k * v[j] + t[j+1][kk]);
    }
    t[j][i] = temp;
   }
  }
  cout << "Case #" << casenum+1 << ": " << t[0][E] << endl;
 }

 return 0;
}

Problem C. Good Luck

いやー、さすが GCJ。出題者側も果敢に挑戦してくるなーと思ったのがこれ。100%の解ではなく一定率以上の正解であれば良い、という問題。実は Small が 1 つだけど複数回可能で、Large が 1 回のみ、というのに特化した Makefile を書いてたりするので今回割と悲しかったりするわけですが、問題読んだ後で「やってくれる」という感じでテンション上がりました。この調子だと近似解を求める問題とかも早晩出てきそうな感じがします。

Small 1 の方は数字が 2, 3, 4, 5 の 4 種類しかないので 3 か 5 が選ばれた場合には確定、2 と 4 の取り扱いをどうするかです。全ての選択パターンから分布をとって判定、とか可能な元の数字の組み合わせから該当するパターンの生起確率をとる、とかが正道な気がしますがベストな解を求める必要はないので手抜きです。素因数分解して 3 と 5 が確定する部分はまず確定。なお情報が全くないところについてはどの数字でも関係ないのでとりあえず 2 で埋める方針です。26, 25 がある場合は 4 が 2 つあることが確定。24 がある時は 2,2,4 とかも有り得ますけど 4,4 の方が確率が高い(はずな)のでこれも 4 が 2 つ。23 がある時は 2,2,2 も有り得ますけど以下同文で 4 が 1 つ。22 の時は残り枚数が 1 枚の時は 4 が 1 つで確定。後は、21 の場合があれば 4 なし。なければ 4 が 1 つです。少なくともここは最適な戦略じゃない気がします。ただ下表のように分が悪い賭でもない、と思いますが、多分。

 積が2積が4
2,2(0,1)
(1,0)
(1,1)
2,4(1,0)(0,1)

一応、これで一回で通っています。ジャッジが正答率出してくれれば良いのにという意見を見ましたが、自分でジャッジ作って試してみるというのが割と良い戦略だったかもしれないですね。

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  cout << "Case #" << casenum+1 << ":" << endl;
  UI R,N,M,K; cin >> R >> N >> M >> K;
  REP(i, R) {
   vector<UI> pr(K); for(auto &val : pr) { cin >> val; }
   int mc2 = 0, mc3 = 0, mc5 = 0;
   vector<UI> dc2(7);
   for(auto val : pr) {
    int c2 = 0, c3 = 0, c5 = 0;
    while(val % 2 == 0) { ++c2; val /= 2; }
    while(val % 3 == 0) { ++c3; val /= 3; }
    while(val % 5 == 0) { ++c5; val /= 5; }
    ++dc2[c2];
    mc2 = max(mc2, c2);
    mc3 = max(mc3, c3);
    mc5 = max(mc5, c5);
   }
   REP(j, mc3) { cout << 3; }
   REP(j, mc5) { cout << 5; }
   int p2 = 3 - mc3 - mc5;
   int c4 = 0;
   if(dc2[6]) c4 = 3;
   else if(dc2[5]) c4 = 2;
   else if(dc2[4]) c4 = 2; //
   else if(dc2[3]) c4 = 1; //
   else if(dc2[2]) {
    if(p2 == 1) c4 = 1; //
    else if(dc2[1] == 0) c4 = 1;
   }
   REP(j, c4) { cout << 4; }
   REP(j, 3-mc3-mc5-c4) { cout << 2; }
   cout << endl;
  }
 }

 return 0;
}

総評

どうにか今年も T シャツ争奪権を得られました。もらえるといいなぁ。

2013年4月14日日曜日

Google Code Jam 2013 Qualification Round

結果

D-Large が Timeout した以外は全部提出しましたが、C-Large が両方落ちて結局 100pt 4217 位でした。落ちる前は 3 桁 rank だったので終了後スコア見たときには D-Large 解いた人そんないるの?と思ったりしてしまいました。C-Large は 15 桁くらいまで bruteforce したリストと照らし合わせた上だったので落ちると思っていなかったのです。が、まぁ、最終結果で確認しろってことですね(後述)。

前振り

今回も C++11 を使って書く、をコンセプトにしています。開始してから gcc version 4.8.1 20130328 (prerelease) (GCC) をダウンロードしてきて使っています。ついでにとりあえず普段は使わない 64 bit exe にしています。共通コードは以下のようなコードです。Boost も使用。Google Code Jam の規定用に URL とバージョンを明示しています。IR というのは range-base for loop 用のものです(Integer Range のイメージ)。for(auto i : IR(1, 6)) とかやると 1,2,3,4,5 でループされます。

// gcc version 4.8.1 20130328 (prerelease) (GCC) at http://www.drangon.org/mingw/
// with -std=c++11

#include <iostream>
#include <sstream>
#include <iomanip>

#include <iterator>

#include <algorithm>
#include <numeric>
#include <utility>
#include <limits>

#include <string>

#include <vector>
#include <deque>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <stack>

#include <tuple>
#include <initializer_list>

#include <cmath>

// Boost library can be retrieved from http://www.boost.org/
// 1.52 is used

#pragma GCC diagnostic ignored "-Wconversion"
#include <boost/range/irange.hpp>
#include <boost/range/iterator_range.hpp>
#pragma GCC diagnostic warning "-Wconversion"

typedef unsigned long long ULL;
typedef long long LL;
typedef unsigned long UL;
typedef unsigned int UI;
typedef unsigned short US;
typedef unsigned char UC;

#define RNG(v) (v).begin(), (v).end()
#define REP(v, e) for(UI v = 0U; v < e; ++v)
#define REP_(v, s, e) for(UI v = s; v < e; ++v)
#define REPV(v, e) for(v = 0; v < e; ++v)
#define REPV_(v, s, e) for(v = s; v < e; ++v)

using namespace std;

template<class Integer>
inline boost::iterator_range< boost::range_detail::integer_iterator<Integer> >
IR(Integer first, Integer  last)
{ return boost::irange(first, last); }

Problem A. Tic-Tac-Toe-Tomek

縦横斜めで各コマの数を数えて判定。Small input の時は↓のように書きましたが、

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases; cin.ignore(numeric_limits<streamsize>::max(), '\n');
 REP(casenum, cases) {
  string result;
  int num = 0;
  int counter[4+4+2][3] = { 0 };
  REP(i, 4) {
   string s;
   getline(cin, s);
   REP(j, 4) {
    if(s[j] == '.') continue;
    int t = 0;
    ++num;
    switch(s[j]) {
    case 'X': t = 0; break;
    case 'O': t = 1; break;
    case 'T': t = 2; break;
    default: assert(false);
    }
    ++counter[  i][t];
    ++counter[4+j][t];
    if(  i == j) ++counter[8][t];
    if(3-i == j) ++counter[9][t];
   }
  }
  REP(i, 10) {
   if(counter[i][0] + counter[i][2] == 4) result = "X won";
   if(counter[i][1] + counter[i][2] == 4) result = "O won";
  }
  if(!result.size()) {
   if(num == 16) result = "Draw";
   else result = "Game has not completed";
  }
  cin.ignore(numeric_limits<streamsize>::max(), '\n');
  cout << "Case #" << casenum+1 << ": " << result << endl;
 }

 return 0;
}

「C++11で書いてないじゃないですかー」ということから一部修正して large input を submit

map<char, int> table = { {'X', 0 }, {'O', 1 }, {'T', 2 } };
// 略
    int t = table[s[j]]; // swtich のところを置き換え

書くだけならこうも書けますが

    int t = map<char, int>{ {'X', 0 }, {'O', 1 }, {'T', 2 } }[s[j]];

毎回 map 作られるのはどうかと思ったのでやめました。

Problem B. Lawnmower

高い方のマスから縦横の高さを確定していく、というコードで1回書いて、別に縦横両方ともその高さで刈る訳じゃないじゃんということで没。実際に提出したコードは↓。縦横で最大の高さをとって(これ以上低く刈ることはできない)、各マスに対して縦横の低い方を取った結果が指定に一致するか、で判定しています。これ C++03 でも通っちゃうんじゃ?と思いましたが、right angle bracket さん(template 指定としての >> の連続)がいるので大丈夫ですね。

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  UI N, M; cin >> N >> M;
  vector<vector<int>> board(N, vector<int>(M, 0));
  vector<int> h(N, 1), v(M, 1);
  REP(i, N) {
   REP(j, M) {
    cin >> board[i][j];
    h[i] = max(h[i], board[i][j]);
    v[j] = max(v[j], board[i][j]);
   }
  }
  std::string result = "YES";
  REP(i, N) {
   REP(j, M) {
    if(board[i][j] != min(h[i], v[j])) { result = "NO"; break; }
   }
   if(result == "NO") break;
  }
  cout << "Case #" << casenum+1 << ": " << result << endl;
 }

 return 0;
}

Problem C. Fair and Square

問題のアレです。繰り上がりが発生しないケースで限定しています。abcba の二乗だと、

876543210
a22ab2ca+b22ab+2bc2a2+2b2+c22ab+2bc2ca+b22aba2

abccba の二乗だと、

109876543210
a22ab2ca+b22bc+2ca2ab+2bc+c22a2+2b2+2c22ab+2bc+c22bc+2ca2ca+b22aba2

と、偶数桁と奇数桁で挙動が違いますが中央桁の 2a2+2b2+2c2 あるいは 2a2+2b2+c2 が一番効いてきます。結局ほとんど 0 と 1 場合によっては 2 もありうるという感じになります。これをパターン分けして数え上げて最初にリストを作ってしまう、というコードが↓です。

vector<string> fair = { "1", "4", "9", "121", "484" };

struct numeric_less
{
 bool operator()(const string &s1, const string &s2) const {
  return s1.size() < s2.size() ||
   (s1.size() == s2.size() && s1 < s2);
 }
};

void mygenerate(int len, const vector<pair<int,int>> &indices)
{
 bool valid = true;
 vector<int> v(len);
 for(auto &val : indices) {
  v[val.first * 2] += val.second * val.second;
  if(v[val.first * 2] > 9) { valid = false; break; }
 }
 REP(i, indices.size()) {
  for(auto j : IR<UI>(i+1, indices.size())) {
   v[indices[i].first+indices[j].first] += 2 * indices[i].second * indices[j].second;
   if(v[indices[i].first+indices[j].first] > 9) { valid = false; break; }
  }
  if(!valid) break;
 }
 if(valid) {
  string s;
  for(auto &val : v) {
   s.push_back(val+'0');
  }
  fair.push_back(s);
 }
}

void init_fair(size_t max_len)
{
 const UI N = (max_len + 1) / 2 + 1;
 REP(i_, N) {
  int i = i_ + 1; // 1 -> (max_len + 1) / 2
  if(i < 3) continue;
  if(i & 1) { // odd
   // 1xx0xx1 x can have 1 at most 6 positions
   //   1.0.1
   //   1.1.0.1.1
   //   1.1.1.0.1.1.1
   //   1.1.1.1.0.1.1.1.1
   // 1xx1xx1 x can have 1 at most 6 positions
   //   1.1.1
   //   1.1.1.1.1
   //   1.1.1.1.1.1.1
   //   1.1.1.1.1.1.1.1.1
   // 1xx2xx1 x can have 1 at most 2 positions
   //   1.2.1
   //   1.1.2.1.1
   // 2.2
   // 2.1.2

   const int X = (i - 1) / 2;
   // 2.2
   mygenerate(2*i-1, { {0,2}, {2*X,2} });
   // 1.0.1
   mygenerate(2*i-1, { {0,1}, {X,0}, {2*X,1} });
   // 1.1.1
   mygenerate(2*i-1, { {0,1}, {X,1}, {2*X,1} });
   // 1.2.1
   mygenerate(2*i-1, { {0,1}, {X,2}, {2*X,1} });
   // 2.1.2
   mygenerate(2*i-1, { {0,2}, {X,1}, {2*X,2} });

   for(auto ii : IR(1, X)) {
   // 1.1.0.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {X,0}, {2*X-ii,1}, {2*X,1} });
   // 1.1.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {X,1}, {2*X-ii,1}, {2*X,1} });
   // 1.1.2.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {X,2}, {2*X-ii,1}, {2*X,1} });
   }

   for(auto ii : IR(1, X)) {
    for(auto jj : IR(ii+1, X)) {
   // 1.1.1.0.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {X,0}, {2*X-jj,1}, {2*X-ii,1}, {2*X,1} });
   // 1.1.1.1.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {X,1}, {2*X-jj,1}, {2*X-ii,1}, {2*X,1} });
    }
   }

   for(auto ii : IR(1, X)) {
    for(auto jj : IR(ii+1, X)) {
     for(auto kk : IR(jj+1, X)) {
   // 1.1.1.1.0.1.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {kk,1}, {X,0}, {2*X-kk,1}, {2*X-jj,1}, {2*X-ii,1}, {2*X,1} });
   // 1.1.1.1.1.1.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {kk,1}, {X,1}, {2*X-kk,1}, {2*X-jj,1}, {2*X-ii,1}, {2*X,1} });
     }
    }
   }

  } else { // even
   const int X = i  / 2;

   // 1xxxx1 x can have 1 at most 6 positions
   //   1..1
   //   1.1..1.1
   //   1.1.1..1.1.1
   //   1.1.1.1..1.1.1.1
   // 2.2

   // 1..1
   mygenerate(2*i-1, { {0,1}, {2*X-1,1} });
   // 2.2
   mygenerate(2*i-1, { {0,2}, {2*X-1,2} });

   for(auto ii : IR(1, X)) {
   // 1.1..1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {2*X-ii-1,1}, {2*X-1,1} });
   }

   for(auto ii : IR(1, X)) {
    for(auto jj : IR(ii+1, X)) {
   // 1.1.1..1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {2*X-jj-1,1}, {2*X-ii-1,1}, {2*X-1,1} });
    }
   }

   for(auto ii : IR(1, X)) {
    for(auto jj : IR(ii+1, X)) {
     for(auto kk : IR(jj+1, X)) {
   // 1.1.1.1..1.1.1.1
    mygenerate(2*i-1, { {0,1}, {ii,1}, {jj,1}, {kk,1}, {2*X-kk-1,1}, {2*X-jj-1,1}, {2*X-ii-1,1}, {2*X-1,1} });
     }
    }
   }

  }
 }
 sort(RNG(fair), numeric_less());
#if 0
 for(auto & val : fair) {
  cout << val << endl;
 }
#endif
}

UI solve(const pair<string, string> &iv)
{
 auto it1 = lower_bound(RNG(fair), iv.first, numeric_less());
 auto it2 = lower_bound(RNG(fair), iv.second, numeric_less());
 UI result = it2 - it1;
 if(it2 != fair.end() && *it2 == iv.second) ++result;
 return result;
}

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 size_t max_len = 0;
 vector<pair<string,string>> iv(cases);
 for(auto & val : iv) {
  cin >> val.first >> val.second;
  max_len = max({max_len, val.first.size(), val.second.size()});
 }
 init_fair(max_len);
 for(auto & val : iv) {
  cout << "Case #" << &val - iv.data() + 1 << ": " << solve(val) << endl;
 }

 return 0;
}

で、どこでミスったかというと

 sort(RNG(fair), numeric_less());
#if 0
 for(auto & val : fair) {
  cout << val << endl;
 }
#endif

が、こうなってました。

#if 0
 sort(RNG(fair), numeric_less());
 for(auto & val : fair) {
  cout << val << endl;
 }
#endif

ああああああああ。まぁ Qualification Round なので通ったからいいんですが。不用意な修正には気をつけろってことですね。

Problem D. Treasure

lexicographically smallest とか言われているのでこりゃ探索するしかないなってんで、どの chest を開いたかによって鍵の数も決まるので探索済みだったら枝刈りする形のコードが↓。これでも small input は通ります。あ、#include <boost/dynamic_bitset.hpp> の追加が必要です。

deque<int> solve(const vector<int> &required, const vector<vector<int>> &got, set<dynamic_bitset<>> &visited, dynamic_bitset<>& open, vector<int> & keys)
{
 if(visited.count(open)) return {};
 if(open.count() == required.size()) return { -1 };
//cerr << "OPENED_COUNT: " << open.count() << endl;
 REP(i, required.size()) {
  if(!open[i] && keys[required[i]] > 0) {
//cerr << "OPEN: " << i << endl;
   open.set(i);
   --keys[required[i]];
   REP(j, got[i].size()) {
    ++keys[got[i][j]];
   }
   auto t = solve(required, got, visited, open, keys);
   if(t.size()) {
    if(t.back() == -1) {
     t.back() = i+1;
    } else t.push_front(i+1);
    return t;
   }
   REP(j, got[i].size()) {
    --keys[got[i][j]];
   }
   ++keys[required[i]];
   open.reset(i);
  }
 }
 visited.insert(open);
 return {};
}

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  UI K, N; cin >> K >> N;
  vector<int> keys(200);
  REP(i, K) { UI t; cin >> t; ++keys[t-1]; }
  vector<int> required(N);
  vector<vector<int>> got;
  REP(i, N) {
   cin >> required[i]; --required[i];
   UI t; cin >> t;
   vector<int> got_(t);
   for(auto &val : got_) {
    cin >> val; --val;
   }
   got.push_back(got_);
  }

  dynamic_bitset<> open(N);
  set<dynamic_bitset<>> visited;
  cout << "Case #" << casenum+1 << ":";
  auto result = solve(required, got, visited, open, keys);
  if(result.size()) {
   for(auto &val: result) {
    cout << ' ' << val;
   }
  } else {
   cout << " IMPOSSIBLE";
  }
  cout << endl;
 }

 return 0;
}

IMPOSSIBLE に対する時間がかかりすぎるので、鍵の数が足りない場合と相互(3つ以上含む)に持ち合っている場合を除外「しよう」としたのが↓コードです(※通りません)。@tanakh さん曰く

ということで、連結してないケース=相互に持ち合ってるケースのイメージなので方針としては間違って無かったようです。手書きメモ上ではグラフみたいなもの書いてたのに(時間もあるのに)適当実装してしまったのが駄目なところですね。

deque<int> solve(const vector<int> &required, const vector<vector<int>> &got, set<dynamic_bitset<>> &visited, dynamic_bitset<>& open, vector<int> & keys)
{
 if(visited.count(open)) return {};
 if(open.count() == required.size()) return { -1 };
//cerr << "OPENED_COUNT: " << open.count() << endl;
 REP(i, required.size()) {
  if(!open[i] && keys[required[i]] > 0) {
//cerr << "OPEN: " << i << endl;
   open.set(i);
   --keys[required[i]];
   REP(j, got[i].size()) {
    ++keys[got[i][j]];
   }
   auto t = solve(required, got, visited, open, keys);
   if(t.size()) {
    if(t.back() == -1) {
     t.back() = i+1;
    } else t.push_front(i+1);
    return t;
   }
   REP(j, got[i].size()) {
    --keys[got[i][j]];
   }
   ++keys[required[i]];
   open.reset(i);
  }
 }
 visited.insert(open);
 return {};
}

bool sanity_check(const vector<int> &required, const vector<vector<int>> &got, const vector<int> &keys_orig)
{
 vector<int> req(200);
 REP(i, required.size()) {
  ++req[required[i]];
 }
 bool flag = false;
 REP(i, required.size()) {
  vector<int> keys(keys_orig);
  REP(j, required.size()) {
   if(i != j) {
    REP(k, got[j].size()) {
     ++keys[got[j][k]];
    }
   }
  }
  bool flag_one = true;
  REP(j, keys.size()) {
   if(req[j] > keys[j]) {
    flag_one = false;
    break;
   }
  }
  if(flag_one) {
   flag = true;
   break;
  }
 }
 return flag;
}

bool sanity_check2(const vector<int> &required, const vector<vector<int>> &got, const vector<int> &keys)
{
 stack<int> q;
 dynamic_bitset<> pushed(200);
 dynamic_bitset<> open(required.size());
 REP(i, keys.size()) { if(keys[i] && !pushed[i]) { pushed.set(i); q.push(i); } }
 while(!q.empty()) {
  int n = q.top(); q.pop();
  REP(i, required.size()) {
   if(required[i] == n && !open[i]) {
    open.set(i);
    for(auto j : got[i]) {
     if(!pushed[j]) { pushed.set(j); q.push(j); }
    }
   }
  }
 }
 return open.count() == required.size();
}

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  UI K, N; cin >> K >> N;
  vector<int> keys(200);
  REP(i, K) { UI t; cin >> t; ++keys[t-1]; }
  vector<int> required(N);
  vector<vector<int>> got;
  REP(i, N) {
   cin >> required[i]; --required[i];
   UI t; cin >> t;
   vector<int> got_(t);
   for(auto &val : got_) {
    cin >> val; --val;
   }
   got.push_back(got_);
  }

  dynamic_bitset<> open(N);
  set<dynamic_bitset<>> visited;
  cout << "Case #" << casenum+1 << ":";
  if(sanity_check(required, got, keys) && sanity_check2(required, got, keys)) {
   auto result = solve(required, got, visited, open, keys);
   if(result.size()) {
    for(auto &val: result) {
     cout << ' ' << val;
    }
   } else {
    cout << " IMPOSSIBLE";
   }
  } else {
   cout << " IMPOSSIBLE";
  }
  cout << endl;
 }

 return 0;
}

総評

C-Large はせっかく合ってたのに感はありますが通過できればいいんです、うん。とにかく楽しかったです。Round 1A も頑張るぞ。