RUPC2016参加記

一日目

去年もチームを組んだomuさんと、くじ引きで引いたnisshyさんと参加した
まずA問題を担当した。一番端に取り付けるのが効率いいしそうしようと考えて実装して通したけど、解説で入力のたびにその反対側に同じ重さだけ取り付ければ良いと聞いて自分の頭の悪さにつらくなった。
D問題で最大値の最小化だし二分探索?何か良い貪欲があるのかな?とか考えていたらomuさんがDPだと言って通したり、E問題でわからないなあこれって考えていたらnisshyさんがメモ化再帰で深さがO(\log N)だから大丈夫だと言って通していたりしているのを横で座っているだけだった。
チームとしてはA,B,C,D,Eの5完だった
ほぼ座ってるだけだった...

二日目

まずB問題を担当した。場合分けミスりそう、サンプルケースが弱いから自分でケースを作らないとダメ、添字が0と1逆になってたとかで時間がかかり30分後ぐらいにACした。
D問題の場合分けのチェックをチームの人と考えたあとF問題を通してるチームが多いからF問題を考えると解法が思いつく。実装して提出するけどWA.チームの人に反転処理が間違ってることを指摘されて修正してAC.算数力がなくてつらい。E問題について考えていたらいつの間にかG問題が通っていてすげーってなる。
残りの時間でE問題とH問題を考えていたけど通せずに終わり。
チームとしてはA,B,C,D,F,Gの6完だった
D問題とかH問題はチーム内で会話しながら解法を詰めていったけどこれは初めての経験で新鮮だった。

三日目

まずB問題を担当した。考えても2重ループになる、約数の個数っていくつだっけ?ローリングハッシュを使えば通せそう?とか考えて疑いながらも実装して投げると通った。
F問題はデータ構造ぽいけど「きっちり」をどう判定するんだって考えていたら、D問題を通した後のkzyKTさんが真ん中で折り返せば良いと言ってすごいなあと感心する。F問題を実装しているのを観察して終了間際にAC. チームとしてはA,B,D,Fの4完だった
真ん中で折り返すって発想が出ないのが自分の弱いところだと認識できた。がんばりたい...

おわりに

チーム戦は個人戦とは違う面白さがあったり強い人の思想を知れたりして楽しかったです。コンテスト終了後も競プロについて話していてモチベが上がりました。
あと最近競プロ始めた学科の人を連れてきたんだけど彼も刺激になったらしくてよかった。
運営や作問の方々ありがとうございました。

オセロAI2

進捗メモ。続くかな

置換表のバグが取れた。バグの原因は同じ局面かつ次に打つ色が異なる場合を同じ状態として持っていたため。このような状態で置換表がバグると考えてはいたけど、実際には起こりえないとして無視していたら実際に発生したっていう。去年の本番直前にバグに気付けてほんとよかった。

Boardの着手可能手の列挙と反転処理の実装にSIMD命令を使ってみたがあまり速くはならなかった。使い方が間違ってるのか、これが普通なのか分からない。FFO40でしかベンチマーク計測してないけど
具体的には、着手可能手の列挙の部分に手を加えると2秒程度の高速化、反転処理の部分に手を加えるとむしろ遅くなった。反転処理の部分は条件分岐を消すために必ず16回ループを回すようにしているのが原因で、SIMD命令を使わない方が速い結果になったんだろうと考えられるけど、着手可能手の列挙の部分は原因不明。

あと、SIMD命令で条件condが真ならbを、偽ならcをaに代入するには以下のようにすればいい

a = (b & cond) | (c & cond)

maskを使えば条件分岐を消せることを知った時は感動した

SIMD命令でプログラムを高速化

AVX命令入門―Intel CPUのSIMD命令を使い倒せ

AVX命令入門―Intel CPUのSIMD命令を使い倒せ

学校でこの本を借りてちょっとかじってみた
1000行1000列の整数行列の積を計算

avx_test.cpp · GitHub

Visual Studio 2015で適当に最適化オプションを加えて実行したらそれぞれの実行速度が24368 [ms]と16447 [ms]になった。単純に8倍にならないのは予想してたけどそれでも思ったより速くならなかった

これ使ってオセロAIのBoardクラスを高速化したい

サイト

x86/x64 SIMD命令一覧表 (SSE~AVX2)

Intel Intrinsics Guide

組み込み関数(intrinsic)によるSIMD入門

オセロAI

学内では毎年12月にオセロAIのコンテストが開催されていて、今年は今月の12日にあった。CODE RUNNERと日程が被ってたから学科の友達にAIだけ渡して参加してもらったら優勝できたらしい。今回は1ヶ月前ぐらいから準備を開始したけどそれでも間に合わなかったから来年は今年実装できなかった部分を実装しきって参加したい(参加資格はB3以下だから次がラストになるし)。

書いたコード
github.com

作るにあたって調べたこととか来年の自分に向けてのメモ

参考にしたサイト

ThellというオセロAIのアルゴリズム解説。すごい詳細に書かれていてわかりやすい。 http://sealsoft.jp/thell/algorithm.html

vsOthaというオセロAIのアルゴリズム解説
vsOtha アルゴリズム解説 - 人生きつね色

東大のISで昔オセロAIの大会があったらしくその資料が置いてあるwiki.パワポを見るとオセロAIについて俯瞰できると思う(色々調べていたら上2つのAIはこの時開発されたものらしくてそれを知った時はとても驚いた)。 http://starlancer.org/~is2004/owiki/wiki.cgi?page=%A4%CA%A4%F3%A4%C7%A4%E2%A5%BB%A5%DF%A5%CA%A1%BC

Zebraという強いオセロAIの解説サイトの翻訳
強いオセロプログラムの内部動作

最初に借りた本。基本的なことから書かれていて読みやすかった
www.amazon.co.jp

Thellの開発者が書いた本らしい
www.amazon.co.jp

やったこと

bitBoard
negaScout
move ordering(速さ優先探索)
中盤12手、終盤20手完全読み
ラスト6手からは通常のnegaAlpha
中盤の評価関数はテーブルベース+着手可能数+確定石の数

やってないこと

定石
Book?
パターンを利用した評価関数の学習
並列処理(YBWC?)
キラー応手
置換表(途中までは実装に入ってたけど3日前にバグに気付き修正する時間はないと決めて取り除いた)
Prob Cut
ラスト1手展開
ラストn手展開
SSE4?を使ったビット演算の高速化

感想みたいなの

オセロについては既に研究し尽くされている感じがあって、調べれば調べるほど情報が出てきて楽しかった。
終盤のAIの高速化に殆どの時間を費やしたと思う。FFO test の40の読み切り時間は9秒だったけど、そこまでの高速化はしていないのに思っていた以上の速度が出たのはプロセッサの進歩のおかげなのかな。あとVisual Studiox64のみに向けたバイナリを生成するようにすると実効速度が2倍速くなったけど、これと同じようなオプションってg++にもあるのかな(-m64がそれっぽいけどコンパイルして実行しても効果を感じることができなかった)。
-std::numeric_limits::min(); がオーバーフローすることに気づかず1週間ぐらい溶かした時はとてもつらかった。
それと前日の最終調整中に、ここで0を代入しているのに、coutすると0以外の数値が出力されるバグを見つけてしまって頭を抱えた。原因はVisual Studioでまずい文字コード(BOMなしutf-8)を使用していたせいで日本語コメントを全て取り除いたら期待通りの動作になった。
初めて単体テストを書いてみた。使ったフレームワークGoogle Test.途中まではテストを書きながらコードを書いてたけど、最後の方は締め切りに追われてテストを書いている時間がなかったのでスケジューリングの重要性を感じた。

来年も勝ちたい

CODE RUNNER 2015 参加記

予選A

ランダムに線分を選ぶんだ時の得点の平均を計算すると3本選んだ時が一番高くて、それを元に山登り法を実装したら開始1時間ぐらいで1位をとったりしてビビった。最終順位は18位まで落ちたけど50位以内に入れて満足だった。

予選B

前の部屋の1位の人が平均何秒待ってるか調べて、自分のそれを真似るみたいなことをしていたけど全く伸びなくて困る。部屋のログを眺めていると1位の人は3回ぐらいしか殴っていなくて、できるだけパワーを溜めるのがいいのかな?という考えになる。相手のHP/2だけのパワーを溜まると殴るようにしたらスコアが伸び始めたけど、気づいたタイミングが遅くて伸びきる前にコンテストが終了した。最終順位は100位ぐらいだったかな?

本戦

来年は交通費を用意できなくて、本戦は辞退したけど今年はお金があったので本戦も参加できた。
問題文を全部読むのに15分ぐらいかかって焦る。APIのリファレンスはtxtとjson両方を読む人はいないだろうからどちらか選べるようにして欲しいと思った。あと参加者のスコアグラフのページにある実数が何を表しているのかわからず、タイトルの"Assign Task"から請け負っている仕事の平均数かなって勘違いしてしまった。貪欲にある仕事が得意な人を降順にソートして仕事に間に合うように社員を割り当てる方針で書く。これで途中3位までに上がるけど、このぐらいのことなら誰でも考えつくだろうし後半失速するだろうなという考えがあったけど順位がいいから中々大きく方針を変更できずにいたら、コンテストが終わっていた。最終順位は20位だった。
順位に関してはコンテスト前は上位50人に入ることができれば十分かなって思っていたので満足しているけど、最初にガチャを回すという発想とか外注で仕事を選ぶ発想が全くなかったのは反省するべき点で、使用していない情報について利用しなくても大丈夫か?みたいなことをちゃんと考えるべきだった。他の競プロの大会と比べるとCODERUNNERはいい順位を取れることが多いし、来年も開催されるなら今年以上の順位を狙いたい。

ソースコード

gist.github.com

ACM-ICPC アジアつくば大会 2015参加記

1日目

スタッフから英語でしか話しかけられなくてビビる
練習セッションの結果キーボードのEndがつらいという話になる。EndをBackSpaceとして使えるようにした
JavaChallengeは基本的な戦略は逃げで、逃げる先は反復深化で決めるみたいなAIを書いたけど、デバッグ出力をcoutに出しているのに気付かないで死んでた。もうちょっとやりたかった
チーム紹介は前でふらふら立っているだけだった
宿舎は水の落ちる音、廊下の音、バイクの音が聞こえてきて1時間ぐらい眠れなかった

2日目

コンテストはAから嘘解法を生やしただけでなく人から問題を奪ったしダメだった
A通した後ずっとCを担当してて、kステップでvからuへ到達できるかみたいな処理を再帰で書いたけど何故かTLEになってずっと悩んでた。隣接行列を累乗する方針にしたらTLEはなくなったけどWAが出続けた
唯一の1完でコンテストが終わってしまった
再帰でTLEしたのはメモ化を忘れていて、行列累乗は懇親会の時に簡単にオーバーフローするから気をつけないとダメだよってことを教えてもらう。直したら通ったからつらい
懇親会の企業ブースはジャバ撃墜ゲームをarukukaさんと一緒にやって正解できた。競プロのHackとは少し違ったから新鮮ですごい楽しかった。そのあとYahoo!のところに行って大学のOBに人に挨拶できたのが良かった
終電が20時とかだったので途中で抜けて帰宅した

終わり

結果は最悪で来年リベンジする以外の言葉が見つからないし、頑張りたい。あと来年も参加できたら懇親会に最後まで居たいし3日目も参加したい

CODE FESTIVAL2015参加記

予選

去年予選落ちして今年通過できるかわかんないなあ状態で予選はドキドキだった
JAG夏合宿で二分探索の問題を解いたおいたおかげか、予選AのD問題で方針が立って全完出来て無事通過できた

1日目

早めに会場入りして交通費精算列を回避した
去年のCODE THANKS FESTIVALで会った人と会場で再会する。予選通過できてよかったですね、みたいなことを言い合ってた
パーカーラインは5完で5完したいなあと思う
ネットワークのトラブルがあって開始が延期したものの始まる

A問題

やるだけな問題があって安心する

int main() {
    string S,T,U;
    cin>>S>>T>>U;
    if(S.size()==5&&T.size()==7&&U.size()==5) cout<<"valid"<<endl;
    else cout<<"invalid"<<endl;
    return 0;
}

B問題

解法がピンと来なくて焦る 多分どこかに偏るだろうから実験すればいいみたいな気持ちになって実験したら規則性が見えて投げる

int main() {
    cin>>N;
    int ans=7;
    if(N==1) {
        cout<<"1"<<endl;
        return 0;
    }
    rep(i,N-2) {
        if(i%2==0) ans+=3;
        else ans+=4;
    }
    cout<<ans<<endl;
    return 0;
}

C問題

多分貪欲?みたいな気持ちになって貪欲を書くけど通らない
落ち着いて自分のやりたいことを整理して書き直すと通った

int N;
int cnt;
string s;

int main() {
    cin>>N>>s;
    rep(i,s.size()-1) {
        if(s[i]==s[i+1]) {
            //cout<<i<<endl;
            cnt++;
        }
        else i++;
    }
    cout<<(cnt+1)/2<<endl;
    return 0;
}

D問題

結構すぐに区間加算と区間最大値を計算出来たらいいことに気付いてkagamizさんのブログからソースを引っ張ってくるけどサンプルが合わない
原因は[r,l)の初期値ミスだった。ちゃんと理解していないとダメだなあって感じ

using lint = ll;
const ll MAX_SIZE = 1<<20;
lint segMax[MAX_SIZE - 1], segAdd[MAX_SIZE - 1];
 
// http://kagamiz.hatenablog.com/entry/2012/12/18/220849
//区間[a, b)に値xを加算する.
void add(int a, int b, int x, int k = 0, int l = 0, int r = 1<<19)
{
    if (r <= a || b <= l) return; //もし交差しない区間であれば終える.
 
    if (a <= l && r <= b){ //もし今みている区間[l, r)が[a, b)に完全に内包されていれば
        segAdd[k] += x;  //区間[l, r)にkを加算する.
        while (k){
            k = (k - 1) / 2;
            //親の区間の最小値は, 子の区間の最小値 + 自分に一様に加算されている値 である.一様に加算される値は更新しなくて良い.
            segMax[k] = max(segMax[k * 2 + 1] + segAdd[k * 2 + 1], segMax[k * 2 + 2] + segAdd[k * 2 + 2]);
        }
        return;
    }
 
    add(a, b, x, k * 2 + 1, l, (l + r) / 2); //子の区間に(必要があれば)xを加算する.
    add(a, b, x, k * 2 + 2, (l + r) / 2, r); //〃
}
 
lint getMax(int a, int b, int k = 0, int l = 0, int r = 1<<19)
{
    if (r <= a || b <= l) return (0);
 
    if (a <= l && r <= b) return (segMax[k] + segAdd[k]); //完全に内包されていれば,その区間の最小値を返す.
 
    lint left = getMax(a, b, k * 2 + 1, l, (l + r) / 2); //子の区間の最小値を求める.
    lint right = getMax(a, b, k * 2 + 2, (l + r) / 2, r); //子の区間の最小値を求める
 
    return (max(left, right) + segAdd[k]); //親の区間の最小値は, 子の区間の最小値 + 自分に一様に加算されている値 である (大切なので2回書きました!!)
 
}
 
ll s[100005],t[100005];
int main() {
    int N;
    cin>>N;
    rep(i,N) cin>>s[i]>>t[i];
    ll s1=s[0];
    ll t1=t[0];
    rep(i,N) {
        add(s[i],t[i],1);
        s1=min(s1,s[i]);
        t1=max(t1,t[i]);
    }
    ll ans=getMax(s1,t1);
    rep(i,N) {
        add(s[i],t[i],-1);
        ans=min(ans,getMax(s1,t1));
        add(s[i],t[i],1);
    }
    cout<<ans<<endl;
    return 0;
}

E問題

貪欲に縮約していけばいいのかな?→C問題と同じような発想だけど大丈夫か??みたいになる
WAもらう→1から書き直す→WAもらうみたいなことを繰り返した後に3ケースだけWAになってそこからサブミットデバッグして通した

void ope1(string &s) {
    string t;
    int cnt=0;
    rep(i,s.size()) {
        if(s[i]=='-') {
            cnt++;
        }
        else {
            t+=string(cnt%2,'-')+s[i];
            cnt=0;
        }
    }
    s=t;
}
 
int cnt_s(string s,char c) {
    int ret=0;
    rep(i,s.size()) if(s[i]==c) ret++;
    return ret;
}
 
int main() {
    string s;
    cin>>s;
 
    int cnt=cnt_s(s,'!');
    if(cnt<1) {
        int cnt2=cnt_s(s,'-');
        s=string(cnt2%2,'-');
        cout<<s<<endl;
        return 0;
    }
 
    string ans;
    rep(i,s.size()) {
        if(s[i]=='!') {
            ans=s.substr(0,i);
            if(cnt%2==0) ans+="!!";
            else ans+=string(cnt%2,'!');
            break;
        }
    }
    ope1(ans);
 
    cout<<ans<<endl;
    return 0;
}

残り30分ぐらいでG問題を考えてたけど全く方針が立たず終わり
5完136位でギリギリパーカーゲット
ペナルティなしだったからか雑にコード書いて投げてWAもらって結果的に時間がかかるみたいなことがあったからもうちょっと慎重にやるべきだった
あと半年ぐらい前から同じような雰囲気の壁を感じているのでどうにか打ち破りたい

トークライブ

けんしょーさんと秋葉さんのを聞いた
AtCoderは普段利用はしていたけど内部事情は全然知らなかったんだなと感じられた
秋葉さんのペアプロは、オイラーツアーという単語は聞いたことあったけどどういうテクニックでどう使うかは知らなかったので勉強になった

エキシビションマッチ

Mi_Sawaさんのvimがライブラリを自動的に挿入する機能があったりUndoの履歴を見ることが出来たりしてすごいなあと思った。1時間で全完するrngさんがヤバだった

去年、CODE THANKS FESTIVALで会ったmatsu7874 さんに晩ご飯に行きましょうと誘われる。ご飯食べながら今日の問題とか作問について話した。
学校の人とそこまでコアなことを話す機会がないからとても楽しい時間だった。
スタンプラリーでギリギリタンブラーを狙える位置だったらしく明日はスタンプを集めようと決める

2日目

朝プロ

すごい雑になっていてA問題からWAを連発してつらかった。D問題のLCSも全く見抜けず3完59位で終了。
悔しい90分でした...

トークライブ

ちょくだいさんとりんごさんのトークライブに行った 最近問題に対してどういうアプローチをして解くかみたいなことを意識するようにしていて、ちょくだいさんのトークライブはそれと被っていて後ろ盾を得たようでよかった
りんごさんのトークライブは、流石という感じでとても楽しく聞かせてもらいました

早解きリレー

順位のmod 20が16だったので、チーム16だった。チーム名は16にちなんで2^2^2になった
問題の担当はD問題で、問題文を読んだけどいまいち解法がわからずcatupperさんに解法を尋ねると、「等差数列だから2n」だと一瞬で返ってきてレベルの差を感じながらもそれが正しくAC
チームとしては最後の問題が1ケースだけ通らずサブミットデバッグしてギリギリ通せた。通した瞬間はとても盛り上がった

閉会式

色んな人が表彰されていた
きゅうりさんのフリー素材がまた増えるなあと思った

このオンサイトをするためにたくさんお金がかかっているだろうなあと参加中ずっと思ってた
来年も開催されるなら絶対に参加したいと思うし、運営の方々ありがとうございました。