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ケースだけ通らずサブミットデバッグしてギリギリ通せた。通した瞬間はとても盛り上がった

閉会式

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

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

sort色々

昇順ソート

const int n=8;
int a[n]={1,3,6,7,4,2,5,8};
vector<int> vec{1,3,6,7,4,2,5,8};

// 昇順
sort(a,a+n);
sort(vec.begin(), vec.end());

// C++11以降
// g++なら-std=c++11オプションをつけてコンパイル
sort(begin(a), end(a));
sort(begin(vec), end(vec));

降順ソート

sort(vec.rbegin(), vec.rend());

// 負の数にしてソート
for(int i=0;i<n;i++) a[i]=-a[i];
sort(a,a+n);
for(int i=0;i<n;i++) a[i]=-a[i];

// ソート後に反転
sort(a,a+n);
reverse(a,a+n);

// greaterを使う
sort(a,a+n,greater<int>());

// ラムダ式 C++11以降
sort(vec.begin(), vec.end(),[](int l,int r){return l>r;});


// C++14以降
// g++なら-std=c++14オプションをつけてコンパイル
sort(rbegin(a), rend(a));
sort(rbegin(vec), rend(vec));

クラスのソート

struct Hoge {
    int a,b,c;
    Hoge(int a,int b,int c) : 
        a(a),b(b),c(c){}
    // operator を定義する
    bool operator<(const Hoge &rhs) const {
        return a<rhs.a;
    }

    // Strict Weak Orderというものがあって、うまく定義しないとダメ
    // http://d.hatena.ne.jp/Cryolite/20040529:embed:cite
    // 複数のものをソートするにはtuple使うのが便利
    bool operator<(const Hoge &rhs) const {
        return tie(a,b,c) < tie(rhs.a,rhs.b,rhs.c);
    }

    // rel_opsなるものがあるらしい(Mi_Sawaさんより)
    bool operator>(const Hoge &rhs) const {
        return rel_ops::operator>(*this,rhs);
    }

};

ICPC2015国内予選

チーム

同じ学科のB2の人とB3の人と出ました
方針はB2の人がA問題、B3の人がB問題、僕がC問題でそれ以降は全員で考えるみたいな感じでした

コンテスト

問題を印刷してA問題を読む(B2の人は競プロ慣れてない人だから一緒に考えるみたいなことにしてた)
去年のA問題ほど複雑ではないなあと思いながら問題概要を伝えて実装の方針を伝える

B問題読んでた先輩に声をかけるとすごい簡単だと言われる
問題概要と方針聞くけど間違ってなさそうだと思う

印刷しておいたC問題とD問題を読む
C問題は頑張って実装すればいいことがわかったけど、D問題は小銭をどう持つかわからないとなった
確実に解けそうなC問題をどう書こうか考えてたら、A問題のサンプルが合わないらしくソースコードを印刷してパソコン交代
先輩がB問題を書き始める
ソースコードを一緒に確認すると、点数が降順に与えられていることが伝わってなかったのと、ギャップが同じ時は人数を最大化できてないことに気付いて指摘する

気付いたら先輩がB問題を通しててA問題の修正も一瞬で終わって2完になる(ここで30分ぐらい)

C問題全然考えてないのにパソコン回ってきてつらくなる

バグる気しかしないなあと思いながら動くものを書いてサンプル食わせると案の定バグっていた
色んなところにprintf仕込みながら必死にバグを取る

1時間ぐらい溶かす

サンプル一致したから提出したらACをもらう

順位表を見ると3完がたくさんいて、残り半分の時間で4問目を通せるかだなあと思う

E問題の問題概要聞くけどよくわからないし、F問題は問題概要は簡単だけど解法わからないってなる

D問題でそれっぽいDP書くけどサンプル合わない
色々してるうちに時間が過ぎてコンテスト終了

コード

gist.github.com

結果

ギリギリ通過していました!

逆元の列挙

takapt0226.hatenablog.com

上の記事の数式が壊れてて式を追うのに時間がかったから、忘れないようにメモ

 m = (m / i) \cdot i + m \% i

 \mod m (-m / i) \cdot i ≡ m \% i

 (m \% i)^{-1} をかけて
 (-m / i) \cdot i \cdot (m \% i)^{-1}≡1

 i^{-1} をかけて
 (-m / i) \cdot (m \% i)^{-1}≡i^{-1}
 i^{-1}≡(m - m / i) \cdot (m \% i)^{-1}

gist.github.com

RUPC2015参加記

初参加でした

一日目

CODE THANKS FESTIVALの会場で会ったことのあるomu さんと再会する。
一緒にチームを組もうという話になって組むことになる。div1:div2を1:2で組みましょうみたいなことを運営の人に言われる。div1の人を募集した結果Mi_Sawaさんがチームに加わることになる。チーム名は全員のHNを合わせた omu_sawa_dan になる。

チーム方針をどうするか話してる時にMi_Sawaさんは普段US配列のキーボードを使用していることがわかる。その結果、Mi_Sawaさんが解法考える人になると宣言する。

コンテストが始まる。

僕がテンプレートを打ち込み、その間に2人が問題を読むという形に。打ち終わる。

omuさんがAを書き始める。

Bを読む。sortして累積和取って二分探索すればいいのがすぐわかる。Mi_Sawaさんに考えた解法を伝えると、もっと簡単な解法があることを教えてもらう。

Aが通らないらしく、印刷してパソコンを交代する。Bを書いて投げる。WAをもらう。印刷してパソコンを交代する。

今の解法だと配列の大きさが64bit分ないとダメだから、sortして累積和取って二分探索の方でいこうと言われる。パソコンを交代する。境界条件面倒って言いながら書く。投げる。通る。

Cを読む。DFSして最小値見つけて掛け算すれば良さそうとわかる。それをMi_Sawaさんに伝えるとUnion-Findを使うほうが楽だと教えてもらう。

Aが通る。Cを書き始める。O(N^2)を投げたけど通らない。次数の下げ方を教えてもらって下げる。投げる。通る。

Dの解法をMi_Sawaさんに解説してもらう。第一段階としてTLE解法をomuさんが実装することになる。その後オーダーを下げる方法を教えてもらいながら僕が実装する。投げる。通る。

EもDPで解けると言われる。解説を聞くが頭が足りないので理解できない。言われるがままに実装する。バグを取る。終了30秒前になってて慌てて提出する。一発で通る。

Mi_Sawaさんプロだった。何度も同じことを質問したけど毎回優しく答えてくれました。人間的にもプロだった。

二日目

この日は5時間セットでdiv1とdiv2は混合しないようにチームを組むことになった。

くじ引きの結果、YuZakuroさんとししゃもさんとチームを組むことになる。チーム名はhopping_heartsになった。両方面識のある人だったから気が楽になる。

チーム方針を話している時にある問題が浮上する。それぞれの普段のプログラミング環境が

YuZakuroさん ししゃもさん
OS Windows Linux OS X
キーボード JIS配列 Dvorak配列 US配列

と全く異なることがわかる。つらい。

コンテストが始まる。

マクロを打ち込む。打ち終わったあとにYuZakuroさんがBはやるだけ宣言をしたのでパソコンを交代する。

Cを読む。ワープする順番は関係ないから、まとめて右に行ったあとにまとめて左に行けばいいと思い、DPで解ける気がする。

Aを投げるけどREする。印刷してししゃもさんにパソコンを交代。ししゃもさんがAを実装する。

Bのコードを読んで配列外参照してるところを見つける。ここ直したら通るだろうってことになる。

Aが難しいらしい。印刷してパソコンを交代する。YuZakuroさんがBを修正して提出する。通る。

YuZakuroさんとししゃもさんにAを任せてCをDPで実装する。投げる。通らない。

印刷してYuZakuroさんにパソコンを譲る。Aが通る。

Dは実験して周期性を見つけて周期ごとにまとめれば良いってことになる。愚直に1日ずつ進める関数を書く。400年周期であることがわかる。実装をししゃもさんに任せて、YuZakuroさんとCを考える。

YuZakuroさんが大きく左右にワープするパターンで落ちることを発見する。

似たような問題を幅優先で解いたことを思い出す。これも幅優先ではと思い始める。

Dがなんかバグっててサンプルが通らないのでパソコンを交代する。Cを幅優先で書く。サンプルが合わない。YuZakuroさんにバグを直してもらう。投げる。TLEする。dijkstraと同じでpriority_queueを使えばいい気がしてくる。queueをpriority_queueにして投げる。通る。

ここで残り1時間ぐらい。

Dのバグ取りに残りを使うことになる。

printfデバッグでバグを探す。曜日判定を行っている

365*y+y/4-y/100+y/400+153*(m+1)/5+d-428;

でオーバーフローしてることがわかる。元々経過日数を計算する式だけど、欲しいのは経過日数の剰余だから途中で剰余とっても問題ないよねってことで式をいじる。サンプルが合う。投げる。通る。

自分が担当したCに時間をかけすぎた...。Eはローリングハッシュ使うんだろうなと思ってたら案の定だったから本番でもっと考えたかった。

チームの人にコード見てもらってバグを修正してもらうのはチーム戦って感じで良かったです。

懇親会のあと会津大の人と信州大の人と一緒に銭湯に行きました。色々お話して勉強になりました。

三日目

一日目と同じくdiv1,div2混合チームにしましょうということになる。

くじ引きの結果、わにさんとぬまさんとでチームを組むことになった。僕以外の二人がMacだったため、チーム名はMacLoversになった。わにさんとは二日目の夜に銭湯に行った時からチーム組めたらいいなと思っていたので組めて良かった。

コンテストが始まる。

わにさんがA、僕がB、ぬまさんがCを読む。

わにさんがAを実装する。投げる。通る。

Bを誤読してて解法がわからない状態だった。わにさんに相談した結果誤読が判明する。Bを実装する。タイプミスしまくる。30分かかる。サンプル一致する。投げる。通る。

Cをぬまさんが実装する。その間にわにさんとD,Eのについて話し合う。

ぬまさんがCを通す。

Dは拡張ダイクストラで解けると思い、実装する。サンプル一致しない(今思うと始点をデクリメントしてなかったせい)。

ぬまさんに、郵便物に関係する頂点について普通のダイクストラして、その後に配る順番を総当りすればいいと言われる。総当りパートをぬまさんが実装する。わにさんのEの解法を聞く。難しそうと感じる。

ぬまさんが総当りパートを実装し終えたので、ダイクストラパートとくっ付ける。投げる。通らない。

INFを10回以上足していることに気付く。intをlong longに変更する。投げる。通る。

コンテストが終わる。

Cは実装重たいって言いながらスッと通したり、Dの総当りパートを実装したぬまさん強いなあと思った。

わにさんがFについて正しい解法を思いついていて、時間があったら解けてた気がする。悲しい。

終わりに

立命館合宿とても楽しかったです。運営側、作問側の方々ありがとうございました。

ローリングハッシュ

ちょっと勉強したのでメモ

Bを定数、長さnの文字列をsとすると\displaystyle \sum_{i=0}^{n-1}  s_i \times B^{n-i-1} \mod M で計算される値をローリングハッシュと言うらしい
 [l,r)での部分文字列のローリングハッシュの値は (H_{0,r} - B^{r-l} \times H_{0,l}) \mod M で計算できる
また、長さmの文字列を後ろにくっつけた時のローリングハッシュの値は、(H_{0,n} \times B^{m} + H_{0,m}) \mod Mで計算できる

ハッシュ値の衝突についてはハッシュを衝突させる話

ソースコード

衝突を起こらないようにするには、複数のmodで計算するのがいいらしい

ローリングハッシュ

例題

AOJ2444
Codeforces Round0291 Div2 C

参考にしたサイト

http://d.hatena.ne.jp/say_hello_to_okaoka/20140204/1391484891

http://algoogle.hadrori.jp/algorithm/rolling-hash.html

http://kmjp.hatenablog.jp/entry/2015/02/15/0900

あり本第二版には載ってるらしい。購入しないとなあ