接着剤の精進日記

競プロでの精進や研究に関係したことを書いていきます。

Codeforces Round #637 (Div. 2)

codeforces.com

A. Nastya and Rice

問題文

n個の穀物がある
穀物1個の重さはa-b \leq x \leq a+bを満たす
n個の穀物の重さの総和がc-d \leq y \leq c+dを満たすことが可能かどうか判定せよ

解法

n (a - b) \gt c+ dもしくは n ( a + b) \lt c - dなら"NO"
それ以外は"YES"

提出コード

void solve(){
    int n, a, b, c, d;
    cin >> n >> a >> b >> c >> d;
    int x = n * (a - b);
    int y = n * (a + b);
    int l = c - d;
    int r = c + d;
    if(!(r < x || y < l)) puts("YES");
    else puts("NO");
}

B. Nastya and Door

問題文

n個の整数からなる数列aと整数kが与えられる
与えられた数列のうち、長さkの連続部分列を考える
この連続部分列の区間を[l:r]で表す
l \lt i \lt r, {} a_{i-1} \lt a_i and a_i \gt a_{i+1}
を満たす個数をpとする
この時、長さkの連続部分列のうち、p+1が最大かつ、lが最小となるような連続部分列を見つけたい
この連続部分列のp+1とlを出力せよ

解法

読解が難しい
長さkで固定なのでしゃくとりっぽく区間の左端を抜いて、右端を追加しながらpのmaxを取っていく
提出コード

void solve(){
    int n, k;
    cin >> n >> k;
    vector<int> a(n);
    REP(i,n) cin >> a[i];
    int minL = 0, right = 0;
    int cnt = 0;
    int ans = 0;
    for(int i=1;i<k-1;i++){
        if(a[i-1] < a[i] && a[i] > a[i+1]) cnt++;
    }
    chmax(ans, cnt);
    for(int i=k-1;i<n-1;i++){
        if(a[i-k+1] < a[i-k+2] && a[i-k+2] > a[i-k+3]) cnt--;
        if(a[i-1] < a[i] && a[i] > a[i+1]) cnt++;
        if(chmax(ans, cnt)) minL = i-k+2;
        // cerr << i << " " << cnt << endl;
    }
    cout << ans + 1 << " " << minL + 1 << endl;
}

C. Nastya and Strange Generator

問題文

長さnの順列が与えられる
以下の操作で順列を作っていくとき、与えられた順列が作成可能がどうか答えよ
1. r_jをまだ埋まっていない順列のマスのindexのうち、最小のindexと定義する j \leq r_j \leq n (1 \leq j \leq n)
2. count_tr_j = tを満たす個数と定義する1 \leq t \leq n
3. まだ埋まっていないマスのうち、count_tが最大のマスを選ぶ
4. そのようなマスが複数あれば、そのいずれかを選ぶ

解法

読解が辛い
言われてる操作を実装すればいい
一回使ったマスは必ず消えるのでsetなどで管理するのが楽そう
また、その次の要素を取得するのもsetで出来る
後は、count_tを取得したいので、セグ木などを使えば出来る
setとセグ木でゴリ押したけどもっと楽な実装ありそう
提出コード

void solve(){
    int n;
    cin >> n;
    vector<pair<int,int>> p(n);
    REP(i,n){
        cin >> p[i].first;
        p[i].second = i + 1;
    }
 
    sort(p.begin(), p.end());
    vector<int> cnt(n+1, 1);
    SegmentTree seg(cnt);
    set<int> st;
    REP(i,n) st.insert(i+1);
    st.insert(INF);
    bool ok = true;
    for(auto [x, idx] : p){
        int ma = seg.getmax(1, n+2);
        // cerr << ma << " " << seg.getmax(idx, idx+1) << endl;
        if(ma != seg.getmax(idx, idx+1)){
            ok = false;
            break;
        }
 
        auto itr = st.find(idx);
        auto next = itr;
        next++;
        // cerr << *itr << " " << *next << endl;
        if(*next == INF) seg.update(*itr, 0);
        else{
            seg.update(*itr, 0);
            int t = seg.getmax(*next, *next + 1);
            seg.update(*next, t + ma);
        }
        st.erase(itr);
    }
 
    if(ok) cout << "YES" << endl;
    else cout << "NO" << endl;
}

D. Nastya and Scoreboard

問題文

n個の数字がスコアボードに表示されている
このうち、k本の棒が正常に表示されていないことがわかっている
今表示されている情報に、丁度k本棒を付け足して作ることが出来る数字のうち、最大のものを答えよ

解法

最初文字列でDPしてたけどMLEとかTLEになるらしい(それはそう)
まず最初に、どの数字を作れるかをboolでDPをする
その時点で丁度k本使って作れないならば"-1"
それ以外は、各桁ごとに最大のものを選びながらDPを復元していけばいい

提出コード

string num[10] = { "1110111", "0010010", "1011101", "1011011", "0111010", "1101011", "1101111", "1010010", "1111111", "1111011" };
 
int needCount(string &s, string &t){
    int cnt = 0;
    REP(l,7){
        if(s[l] == '1' && t[l] == '0'){
            cnt = -1;
            break;
        }
        if(s[l] == '0' && t[l] == '1') cnt++;
    }
    return cnt;
}
 
void solve(){
    int n, k;
    cin >> n >> k;
    vector<string> s(n);
    REP(i,n) cin >> s[i];
    vector<vector<bool>> dp(n+10, vector<bool>(k+10, false));
    vector<vector<bool>> pre(n+10, vector<bool>(k+10, false));
    dp[0][0] = true;
    REP(i,n){
        REP(j,k+1){
            if(!dp[i][j]) continue;
            REP(l,10){
                int cnt = needCount(s[i], num[l]);
                if(cnt == -1) continue;
                if(j + cnt > k) continue;
                dp[i+1][j+cnt] = true;
            }
        }
    }
    if(!dp[n][k]){
        cout << -1 << endl;
        return;
    }
 
    pre[n][k] = true;
    for(int i=n;i>0;i--){
        REP(j,k+1){
            if(!dp[i-1][j]) continue;
            REP(l,10){
                int cnt = needCount(s[i-1], num[l]);
                if(cnt == -1) continue;
                if(j + cnt > k) continue;
                if(!pre[i][j+cnt]) continue;
                pre[i-1][j] = true;
            }
        }
    }
 
    string ans = "";
    int cur = 0;
    REP(i,n){
        int ma = -1;
        int next = -1;
        REP(j,10){
            int cnt = needCount(s[i], num[j]);
            if(cnt == -1) continue;
            if(cur + cnt > k) continue;
            if(!pre[i+1][cur + cnt]) continue;
            if(chmax(ma, j)) next = cur + cnt;
        }
        ans += char(ma + '0');
        cur = next;
    }
    cout << ans << endl;
}

おわりに

読解が辛い回だった…

Codeforces Round #636 (Div. 3)

codeforces.com

A. Candies

問題文

n個のキャンディがある
Vovaは1日目にx個、2日目に2x個、3日目に4x個…とキャンディを買いました
この時 x + 2x + 4x + ... + 2^{k-1}x = nを満たすxを出力せよ
ただし、k k \gt 1を満たす

解法

左辺が 2^{k} - 1なのでkは最大でも30程度なので全探索すればいい
提出コード

void solve(){
    ll n;
    cin >> n;
    ll cnt = 3;
    ll base = 2;
    while(cnt <= INF){
        if(n % cnt == 0){
            cout << n / cnt << endl;
            return;
        }
        base *= 2;
        cnt += base;
    }
}

B. Balanced Array

問題文

2で割り切れる正の整数nが与えられる
以下の条件を満たすような数列を構築したい
1. 最初の\frac{n}{2}個の要素は2で割り切れる
2. 2つ目の\frac{n}{2}個の要素は2で割り切れない
3. 全ての要素は正で、全て異なる値
4. 最初の\frac{n}{2}の要素の合計と2つ目の\frac{n}{2}個の要素の合計は等しい
このような数列が構築可能ならばその一つを出力せよ

解法

\frac{n}{2}が奇数ならば、構築できない
偶数ならば必ず構築できるので、偶数は適当に
奇数は最後の1要素で合計が等しくなるように調整すればいい
提出コード

void solve(){
    int n;
    cin >> n; // n is even
    if((n / 2) % 2 == 1){
        cout << "NO" << endl;
        return;
    }
    vector<int> even, odd;
    ll sum = 0;
    for(int i=2;i<=n;i+=2){
        even.push_back(i);
        sum += i;
    }
    for(int i=1;i<n-2;i+=2){
        odd.push_back(i);
        sum -= i;
    }
    odd.push_back(sum);
    cout << "YES" << endl;
    for(auto x : even) cout << x << " ";
    for(auto x : odd) cout << x << " ";
    cout << endl;
}

C. Alternating Subsequence

問題文

長さがnの数列aが与えられる
その部分列を考えたときに、隣り合う要素の符号が異なるものを考える
この時、その部分列の長さが最大となるように部分列を構築する時、その総和が最大を出力せよ

解法

符号ごとにランレングス圧縮のような感じで圧縮する
符号が同じ場合はその要素のmaxを保存し、符号が変わったらmaxを部分列に追加し、同様に続けていく
提出コード

void solve(){
    int n;
    cin >> n;
    vector<ll> a(n);
    REP(i,n) cin >> a[i];
    vector<ll> res;
    ll cur = a[0];
    for(int i=1;i<n;i++){
        if(cur * a[i] < 0){
            res.push_back(cur);
            cur = a[i];
        }
        else chmax(cur, a[i]);
    }
    res.push_back(cur);
    int sz = size(res);
    
    ll ans = 0;
    REP(i,sz) ans += res[i];
    if(sz == 0) ans = -1;
    cout << ans << endl;
}

D. Constant Palindrome Sum

問題文

n個の整数からなる数列が与えられる
任意の回数、a_i [1;k]に置き換える操作が行える
以下の条件を満たすように操作を行った時、操作回数の最小を答えよ
1. 全てのa_ik以下を満たす
2. 1 \leq i \leq \frac{n}{2}に対し、 a_i + a_{n-i+1} = xを満たす

解法

\frac{n}{2}個のペアごとに何回の操作でどの範囲の区間の値に変更できるかを考える
そうするとimos法が使えることがわかるので
最大2回まで操作を行えば十分なので、1回の操作で作れる区間と2回の操作で作れる区間をimos法で前計算をする
その後、xをどの値にするかを全探索すればいい

提出コード

void solve(){
    int n, k;
    scanf("%d %d", &n, &k);
    vector<int> a(n), imos(2*k+10);
    REP(i,n) scanf("%d", &a[i]);
 
    REP(i,n/2){
        int x = a[i], y = a[n-i-1];
        if(x > y) swap(x, y);
        int sum = x + y;
        int lt = sum - (y - 1);
        int ut = sum + (k - x);
        imos[lt]++, imos[sum]--;
        imos[sum+1]++, imos[ut+1]--;
        if(lt != 2) imos[2] += 2, imos[lt] -= 2;
        if(ut != 2 * k) imos[ut+1] += 2, imos[2*k+1] -= 2;
    }
    REP(i,2*k) imos[i+1] += imos[i];
    int ans = INF;
    for(int i=2;i<=2*k;i++) chmin(ans, imos[i]);
    cout << ans << '\n';
}

E. Weights Distributing

問題文

n個の頂点とm個の辺からなる無向グラフとm個の重みpが与えられる
頂点aから頂点bに行き、その後頂点bから頂点cに移動することを考える
この時、通った辺の重みが最小となるように与えられたpを各辺に割り振っていく
このようにして達成できる重みの総和の最小値を答えよ

解法

aからb、bからcへ移動することを考えると中継点が存在することがわかる
そのため、この中継点からの距離を最小となるようにすればいい
a,b,cからの最短距離をbfsなどで前計算しておく
後はpを昇順にソートし、累積和を取っておくと各中継点からの重みの総和を求めることが出来る
提出コード

void solve(){
    int n, m, a, b, c;
    cin >> n >> m >> a >> b >> c;
    --a, --b, --c;
    vector<int> p(m);
    vector<ll> cum(m+1);
    REP(i,m) cin >> p[i];
    sort(p.begin(), p.end());
    REP(i,m) cum[i+1] = cum[i] + p[i];
    
    vector<vector<int>> G(n);
    REP(i,m){
        int u, v;
        cin >> u >> v;
        --u, --v;
        G[u].emplace_back(v);
        G[v].emplace_back(u);
    }
    vector<int> da(n, INF), db(n, INF), dc(n,INF);
    da[a] = db[b] = dc[c] = 0;
    auto bfs = [&](int s, vector<int> &d) -> void{
        queue<int> q;
        q.push(s);
        while(!q.empty()){
            int cur = q.front();
            q.pop();
            for(auto nv : G[cur]){
                if(d[nv] != INF) continue;
                d[nv] = d[cur] + 1;
                q.push(nv);
            }
        }
    };
 
    bfs(a, da);
    bfs(b, db);
    bfs(c, dc);
 
    ll ans = LINF;
    REP(i,n){
        if(da[i] + db[i] + dc[i] > m) continue;
        chmin(ans, cum[db[i]] + cum[da[i] + db[i] + dc[i]]);
    }
    cout << ans << endl;
}

おわりに

中継点を全探索するのは第一回のPASTにあったね、忘れてた

AtCoder Beginner Contest 163(ABC163)

atcoder.jp

unratedになっちゃったね、悲しい
Dが普通にわからなかったのでもっと悲しい…

A - Circle Pond

 2 \pi Rを出力すればいい
10^{-2}以下は許容されるから\pi = 3.14とかでも大丈夫そう(多分)
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    double R;
    cin >> R;
    printf("%.12lf\n", 2 * R * PI);
}

B - Homework

N \lt sumなら -1
そうでないならN - sum
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N, M;
    cin >> N >> M;
    ll sum = 0;
    vector<int> A(M);
    REP(i,M){
        cin >> A[i];
        sum += A[i];
    }
    if(sum > N) cout << -1 << endl;
    else cout << N - sum << endl;
}

C - management

一瞬Cで部分木のサイズ求めさせるの?と誤読した
ちゃんと問題を見ると出てきたものをカウントしてあげればいい
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N;
    cin >> N;
    vector<int> cnt(N+1);
    REP(i,N-1){
        int a;
        cin >> a;
        cnt[a]++;
    }
    for(int i=1;i<=N;i++) cout << cnt[i] << endl;
}

D - Sum of Large Numbers

これ難しくないですか?全然わからなかった
まず、i個取ったときのことを考える
この時、小さい順にi個取って出来る最小値と、大きい順に取った最大値を作る
そうするとその[最小値, 最大値]の区間の数字は全て作ることが出来る
これ、非自明だと思うんだけどAtCoderの民強い…
終わった後に思い出したんだけど同じような考え方の過去問が次のやつ
こういう発想全然出来ないなあ
atcoder.jp

提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N, K;
    cin >> N >> K;
    vector<ll> left(N+1), right(N+1);
    REP(i, N+1){
        left[i] = i;
        right[i] = N - i;
        if(i > 0) left[i] += left[i-1], right[i] += right[i-1];
    }
    ll ans = 0;
    for(int i=K;i<=N+1;i++){
        ans += right[i-1] - left[i-1] + 1;
        ans %= MOD;
    }
    cout << ans << endl;
}

E - Active Infants

これも難しい
制約的に \mathcal{O}(N^2)が出来る
基本的には大きい方から決めていけば良さそうだけど貪欲だと死ぬなあと考えていた
実際その通りで、大きい順から左右に割り振っていったときの状態を持ちながらDPすればいい
ここまでわかってもコードに起こすの中々しんどいのでDP特訓が必要…
提出コード

ll dp[2020][2020];

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N;
    cin >> N;
    vector<pair<ll, ll>> A(N);
    REP(i,N){
        cin >> A[i].first;
        A[i].second = i + 1;
    }
    sort(A.rbegin(), A.rend());
    memset(dp, -1, sizeof(dp));
    dp[0][0] = 0;
    REP(i,N){
        for(int l=0;l<=i;l++){
            int r = (i - l);
            if(l + 1 <= N && ~dp[l][r]) chmax(dp[l+1][r], dp[l][r] + A[i].first * abs(A[i].second - l - 1));
            if(r + 1 <= N && ~dp[l][r]) chmax(dp[l][r+1], dp[l][r] + A[i].first * abs(A[i].second - (N - r)));
        }
    }
    ll ans = 0;
    REP(i,N+1) chmax(ans, dp[i][N-i]);
    cout << ans << endl;
}

F - path pass i

TLで流れてたのを見ただけなんだけどマージテク解があるらしい
マージテクは何かUnionFindに使われてるやつだよね、という認識でしかないのでちゃんとやります

おわりに

unratedで冷えるの一番悲しくない?
ratedだと悔しさのほうが強いから精進欲起きてマイナスとは思わないんだけど
ともあれ、冷えたのには変わりないので精進したいね
これから1ヶ月くらいマラソン系のコンテストたくさんあるんだけど…

Codeforces Round #635 (Div. 2)

codeforces.com

A. Ichihime and Triangle

問題文

正の整数a, b, c, dが与えられる
次の条件を満たす正の整数x, y, zのうち、三角形となるような3つ組を出力せよ

解法

難しすぎる
三角不等式を考えるとb, c, cなどを出力すればいい
提出コード

void solve(){
    ll a, b, c, d;
    cin >> a >> b >> c >> d;
    cout << b << " " << c << " " << c << endl;
}

B. Kana and Dragon Quest game

問題文

正の整数x, n, mが与えられる
以下の2つの操作が行える
1. h\lfloor \frac{h}{2} \rfloor + 10にする
2. hh - 10にする
1の操作を最大n回まで、2の操作を最大m回まで行える時hを0以下に出来るかどうかを判定せよ

解法

hが20以上の時、1の操作を行っても意味がなく、10未満だとむしろ悪くなる
そのためhが20以上の時に1の操作を行い、その後2の操作を行って0以下に出来るかを判定する
提出コード

void solve(){
    int x, n, m;
    cin >> x >> n >> m;
    while(n > 0 && x >= 20){
        x /= 2;
        x += 10;
        n--;
    }
    if(x <= 10 * m) cout << "YES" << endl;
    else cout << "NO" << endl;
}

C. Two Teams Composing

問題文

n頂点からなる木が与えられる
n頂点のうち、k個の頂点を選ぶことを考える
この時、選んだ頂点から頂点1までのパスのうち、選んだk個の頂点以外の頂点の総和を最大化したい
この最大値を答えよ

解法

葉から見ていくことを考える
この時、頂点までの距離が加算される
全ての葉が埋まった状態で、次に頂点を追加する時、その頂点は葉からのパスに含まれるため、子のサイズ分引いてあげる必要がある
そのため、予め各頂点の部分木の大きさをとその深さを求めておき、その差分が大きい順に取っていけばいい
本番では葉からbfsしたけど最初にpriority_queueに入れて大丈夫そう
提出コード

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int n, k;
    cin >> n >> k;
    sz.resize(n);
    G.resize(n);
    vector<int> deg(n);
    REP(i,n-1){
        int u, v;
        cin >> u >> v;
        --u, --v;
        G[u].push_back(v);
        G[v].push_back(u);
        deg[v]++, deg[u]++;
    }
    LCA<vector<vector<int>>> lca(G);
    lca.build();
    priority_queue<tuple<int,int,int>> pq;
    ll ans = 0;
    dfs(0, -1);
    // REP(i,n) cout << sz[i] << " ";
    // cout << endl;
    vector<bool> used(n);
    for(int i=1;i<n;i++) if(deg[i] == 1) pq.emplace(lca.dist(0,i)-(sz[i] - 1), i, -1);
    while(k > 0 && !pq.empty()){
        auto [dist, cur, par] = pq.top(); pq.pop();
        if(used[cur]) continue;
        ans += dist;
        k--;
        used[cur] = true;
        for(auto nv : G[cur]){
            if(nv == par) continue;
            pq.emplace(lca.dist(0, nv)-(sz[nv] - 1), nv, cur);
        }
    }
    cout << endl;
    cout << ans << endl;
}

D. Xenia and Colorful Gems

問題文

赤、緑、青のgemがそれぞれ n_r, n_g, n_b個あり、それぞれの重さはr_i, g_i, b_iである
各gemを一つずつ選び次の式を最小化することを考える
 (x - y) ^ 2 + (y - z) ^ 2 + (z - x) ^ 2
この時、得られる値のうち最小のものを出力せよ

解法

こういうのはとりあえず1個固定することを考える
その後、式を見ると最小化にするためにはx = y = zに近づける必要があるのでlower_boundなどで探索すればいい
本番では赤を固定して、左右を探索してたんだけど、これだけだと不足っぽい
よく考えると3色の並べ方は3!しかないので全部のパターンを試せばいい
提出コード

void solve(){
    int nr, ng, nb;
    cin >> nr >> ng >> nb;
    vector<ll> R(nr), G(ng), B(nb);
    REP(i,nr) cin >> R[i];
    REP(i,ng) cin >> G[i];
    REP(i,nb) cin >> B[i];
    sort(R.begin(), R.end());
    sort(G.begin(), G.end());
    sort(B.begin(), B.end());
    ll ans = 3 * LINF;
    auto f = [&](ll a, vector<ll> &b, vector<ll> & c) -> void{
        auto itr1 = upper_bound(b.begin(), b.end(), a);
        auto itr2 = lower_bound(c.begin(), c.end(), a);
        if(itr1 == b.begin() || itr2 == c.end()) return;
        itr1--;
        ll tmp = (a - *itr1) * (a - *itr1) + (a - *itr2) * (a - *itr2) + (*itr1 - *itr2) * (*itr1 - *itr2);
        chmin(ans, tmp);
    };
    
    REP(i,nr){
        f(R[i], G, B);
        f(R[i], B, G);
    }
    REP(i,ng){
        f(G[i], R, B);
        f(G[i], B, R);
    }
    REP(i,nb){
        f(B[i], R, G);
        f(B[i], G, R);
    }
    cout << ans << endl;
}

おわりに

最近のdiv2 Aほんとむずかしい

AtCoder水色の接着剤による就活ポエム

はじめに

接着剤ことボンド(@bond_cmprog)が就職活動を終えたので意外と需要があるっぽい就活ポエムを書きます
簡単にまとめると、競プロ(AtCoder)だけで就活したというよりは、競プロ(AtCoder) + CodinGame + マラソンコンテスト + 研究の掛け算で殴ったような感じです
後述しますが、研究とCodinGameが特にぼくが受けたところでは評価されたと思っています
全人類CodinGameやって就活しような!
お世辞にも参考になるようなムーブをしているわけではないので(むしろ反面教師?)
あくまでポエムとしてお楽しみください
参考にした結果不利益を被っても責任は負いません()
参考までに現時点でのAtCoderのレート遷移を載せておきます
f:id:tkm-kyudo:20200416070920p:plain

目次

自己紹介

以前、入水記事(AtCoderで水色になるまでにやったこと - 接着剤の精進日記)でも自己紹介しましたが、はじめましての方もいらっしゃると思いますので、就活で使った資格なんかも交えて改めて書きます

学部時代

学部時代は文系で商学部でした
とはいっても名ばかりで商学系の科目はあまり取らず、情報系の授業が取れるのでそれらをメインで履修していました(簿記は結構好きでした)
学部3年の始めくらいまでは部活(弓道)ばかりやっていて、AtCoderはもちろんプログラミングする機会はまったくありませんでした(高校時代に少し触っていた程度)
その甲斐もあってか弓道は三段を取得することができて、面接の時の話のネタになりました
弊学部は2年後期にゼミ配属(研究室配属ではない)されるんですが、そこの先生がたまたま自然言語処理の研究をされている方で、興味がある人向けに自然言語処理を教えてくれて、それで興味を持ってまたプログラミングをやり始めました
その延長線上で、pythonを使ったクロール、スクレイピング機械学習などをやり始め、学部3年の冬に言語処理学会という自然言語処理の学会があるのですが、そこで研究を発表しました(なんで?)
こんな感じで研究に興味を持ったので院進する決意をしました(のと人生について考える時間がちょっと欲しかった)
理系の院に進むことになるのですが、学部時代にまったくやったことのない科目ばかりでしたので、学部3年の3月くらいから院試の勉強をしてました
が、途中で飽きてしまったので6月くらいにAtCoderを本格的にはじめました(はい?)
競プロ初めて水色になるあたりまでは、興味があれば入水記事(AtCoderで水色になるまでにやったこと - 接着剤の精進日記)を参照していただければと思います
なんやかんや無事院試が受かったので、今に至ります
ちなみに、学部時代のインターンや開発アルバイトの経験などは皆無です
ところで、学部時代1年間くらい彼女がいました、別れました
彼女と別れる ⇒ 競プロを始める は真ですね(?)
今は競プロに片思い中です
AtCoderは最近デレて来てるんですが、こどふぉがツンツンです、困りました

院に入ってから

入って1, 2ヶ月はちゃんと(?)研究をしていたんですけど、入水してから競プロ沼に完全に堕ちてしまいました
とはいっても、一応研究発表はしていて、修士1年の間では業績としてかける成果は3つほどありました
M1の夏頃にインターン受けないとやばいかなーと思って色々調べました
が、興味の持てるところがあまりなくて結局AtCoderJobsにも載っているところを1社受けました、落ちました(悲しい)
フィードバックを貰っていないので落ちた原因はわかりませんが、当時のスキルセットで落ちたというよりはやりたいことがあまり明確ではなかった(面接でも聞かれたけどふわっとしか答えられなかった)のが原因かなーと思っています
暖色でつよつよの人ならポテンシャルだけで取ってもらえる可能性は上がりそうですが、水色前後とかの人ならやりたいことを明確にするのがいいんじゃないかなーって思います(水色でやりたいことが明確で、その会社とマッチしてる人材って強いと思う)
そんな感じで、インターン経験0で就活に望みました
アルバイトはしてたといえばしてたんですけど、指導教員のタスクをやるような感じで、内容としては学部時代からやっていたスクレイピングでデータ収集・整形などがメインでした
長くなってしまったのでこの辺りで区切って就活編に進みます

就活編

前置きが長くなってしまいましたが、ポエムなので大目に見てください
就活編は資格なんかのスキルセット、就活前にやったところ、実際に受けたところの3つに区切って書いていこうかなと思います

資格(スキルセット)

履歴書とかに書いた資格などを書きます

だいたいこんな感じです
ぼくの持つ他の競プロerのイメージと比較すると、研究やCodinGameの成績が強みになるかなという感じです
面接のときには弓道やスキーの資格が意外と食いつき良かったです
TOEICは一般と比べると良いほうだけど、まあ可もなく不可もなくで面接でも特に言及はされてないですね

CodinGameって?

何回かCodinGameについて言及しているので、この辺で説明しておこうかなと思います
簡単に言えばゲームのプラットフォームが用意されているので、ゲームAIを作って強い人が勝ちです

f:id:tkm-kyudo:20200416042601p:plain
TRON BATTLE
f:id:tkm-kyudo:20200416042754p:plain
HYPER SONIC

こんな感じでUIがかなりクオリティが高いので眺めてるだけでも結構楽しかったりします
もし興味が湧いた人は次の記事が参考になると思うのでぜひやりましょう
qiita.com iwashi31.hatenablog.com

ランクが上から順位にLEGEND、GOLD、SILVER、BRONZE、WOODというふうになっているんですが、TRON BATTLEが初心者におすすめでSILVERくらいならすぐ競プロerならなれると思います(競プロ知識も使える)
CodinGameの宣伝したいためだけにポエム書き始めたみたいなところもあるんですけど、後述しますがCodinGameの成果で就活出来たみたいなところもあるのでみんなもやろう!
ラソン系コンテストに参加したことがあって、楽しいと思える人は多分ハマると思います(副次効果として競プロの実装がかなり楽になります)
今、丁度コンテストがあるんですけどもう終わってしまうので、5月8日に新しいコンテストがあるのでぜひ出ましょう
人生に悩んでいたら今やっているコンテスト全然やれてないので残り日数でもうちょっとがんばります…
www.codingame.com

就活前にやったこと

最初に書いておくと、ほとんど何もやっていません(おいおい)
一応ちゃんと書くと2月にキーエンスコンの新卒向けイベントが棚ぼたで降ってきて、交通費が出るので日帰り大阪旅行をしました
他には競プロerに馴染み深いフューチャーの競プロer向け説明会があったのでそれにも参加して色んな人とお話してきました
この時、他の人ともお話する機会があって、そこでCodinGameを教えてもらってやり始めました
その後3月の中頃までCodinGameにハマって就活をほぼ何もせずそればっかりやっていました(ええ…)
AtCoderCodeforcesとyukicoderは基本全部出て、有志コンがあったらそれも出て、3月にtopcoderのMMがあったのでそれも出ました(?)
全く関係ないんですけどこの表現がとても好きです
f:id:tkm-kyudo:20200416065143p:plain 一応2月29日にAtCoderJobsをポチりました、ギリギリまで何もしていないですね、真似しちゃだめですよ
就活期間に入っちゃうんですけど、AtCoderでの就活イベントがあったのでそれに申し込みました
ただ、コロナの影響でなくなってしまいました
その後、メールで企業の方から何件か面談のお誘いがあったので企業の方とお話しました
ぼくは結局有効活用しなかったんですけど、いろんな企業さんとお話できるし、競プロに理解のある企業が多いと思うので来年もあるかわかりませんが、あったら参加するのがいいかなって思います
atcoder.jp

受けた企業

ここからは実際に受けた企業と、面接で聞かれたことなんかを書いていきます
とは言っても最終までちゃんと受けたのが1社だけなので参考にならないですごめんなさい…
受けたところ全般の感想としては、よくある一般的な面接のイメージとは違って、自分のスキルセットに対して、特に自分の場合研究について聞かれることが多かったです
他には、自分のやりたいことや技術に対する考え方などについて聞かれるので、しっかり対話しながら伝えられたら大丈夫かなという印象でした
どの面接も、マッチング度合いを見ている感じで、能力的というよりはこの会社にあっているかどうかを見られていた気がします(し、そういう意図だよてきなことを言っていました)
ところで、面接で志望動機を言った記憶がありません(ほんとに聞かれなかった)
イニシャルは実際の企業名と関係ないです、内容もどこまで書いて良いのかわからなかったのでかなーり本質部分を削っています
Jobsとかにも掲載されている競プロと関係の有りそうなところだけ抜粋します

  • A社
    この企業は一次面接だけ受けました
    内定を貰った企業の選考をしている間、うだうだして面接の予約をせずにいたら内定が出たので、申し訳ありませんが辞退しました
    内容的にはかなり好感をもっていて、競プロerが働きやすそうな環境だなと勝手に思っています
    コーディングテストがあったんですけど、個人的にかなり好きな問題でした
    一次面接の流れとしては、最初にお互いの自己紹介をしてその後研究についての質問がメインでそれに答えるという感じでした
    その後、自分から逆質問をいくつかして競プロの認知度合いや、この企業で働いてよかったことを聞きました(最初の自己紹介で10年くらいここに勤めているという情報があったので)
    競プロはかなり認知されているみたいで、働いてよかったことはこの企業の気質があっていて、働きやすいみたいなことを言っていたと思います
    最後に、どういうことに興味があるのか、アカデミア系は考えている?みたいなことを聞かれて、マラソン系のような最適化に興味があって働きたいので考えてませんみたいなことを言いました
    最後に是非2次面接受けてねと宣伝されました(まだ一次面接終わってないのですが)
    ここの一次面接だったか少し記憶が怪しいんですけど、どういうエンジニアになりたいのかみたいなことを聞かれました
    基礎研究みたいなことをやりたいのか、がっつり開発やりたいのか、お客さんとお話してお客さんの問題を見つけてそれに取り組むのか、みたいな感じだったと思います
    自分は結構お話するのは好き(得意とは言っていない)なので、お客さんのところでお話するのも良いなと思い、この辺りからそういった方向性みたいなのが定まったような気がします
    もちろん、つよつよになりてえという思いは大前提として

  • B社
    AtCoderでの就活イベントがなくなってからメールで連絡を受けた企業さんです
    ここの企業名を面談するまでしらなかったんですけど、結構TLでも見るようなサービスを提供している会社で、働きやすそうな環境だなあと思いました
    面談の際にお話した感想としては、技術者を大切にしているイメージが強かったですね
    技術が好きで放っておいてもやってくれる人を雇って、そういった人たちが働いてくれるような環境を用意するみたいなことをいってました
    面談のときに競プロerいますか!?と聞いたら競プロ部があるみたいな話をしていたと思います
    今年の新卒入社にマラソン系やっている人いるよと言われておっ!?ってなりました
    お話した感じ結構好感度が高かったんですけど、コーディングテストの問題文がclar投げたくなってしまったので結局応募せず見送り…(ごめんなさい)

  • C社
    内定を頂いたところです
    紹介されるまでまったく知らなかったんですけど、聞いてみるとすごい人がたくさんいたり、提供サービスや名前を知っているエンジニアが結構いてびっくりしました
    書類選考通ってから聞いたんですけど、書類の段階で結構落とされるみたいです(怖い)
    CodinGameの成績と研究の成果が評価されたのかなという印象です
    面接は3回あって、一次では会社のざっくりした説明と研究ややりたいこと、どんなことをしたいかなどを聞かれました
    逆質問は、今やっている事業についてHPでわからなかったことや今後どういう領域に手を出したいかなどを聞いていた気がします
    二次では、エンジニアの方が2人いて、詳しい会社の説明をメインで受けて、その方々からも研究についての質問が多かったです
    事前にどういうことやっているエンジニアと話したい?みたいなことを言われていてその希望を伝えていたので、話してみたいことをそれぞれ考えていました
    そのそれぞれに対してタスクの大変なこととか、自分もやったことがあるので難しいと感じたところや、どういったところから着手しているのかを聞きました(面接というよりは純粋に技術に対する質問ですね)あとは、アカデミアからエンジニアになった方だったので、アカデミアと実務とのギャップを聞きました
    最終が今までで一番面接っぽかったです
    とはいっても奇をてらったような質問ではなく、相手の人柄を知るような意図の質問で、相手のいいところを知りたい、引き出してあげたいみたいなことを言っていました(いい面接官だ…)
    基本的には、研究についてがやっぱりメインで、学部時代どういうことやっていたの、とか弓道について雑談したりとか、自己PRしたりとかをしました
    あとは、ぼくの経歴が少し特殊(?)なのでどういう経緯で文系から理系の院に進んだのみたいなお話をしました
    逆質問では、経営理念についての質問を1個しました、内容としてはこういう経営理念だけど現場の人はどれくらい興味を持っているのか、みたいなことを聞きました(どうでもいいですが、良い質問だねと言われてちょっとうれしかったです)
    最終を受けて3営業日後くらいに内定通知が来て、自分のやりたいことと環境が自分に一番合ってると思ったのでここに決めました
    つよつよの人がたくさんいるのでつよくなりたいね

  • D社
    こちらも競プロerが働きやすそうな環境だと思います
    説明会がWebで聞けたので、参加しました
    やっている内容としては、自分が興味の持っている今までの企業と少し離れているかなって感じがあって、興味ないわけではないけどもっとやりたいことがあったので応募は見送りました
    もう少しお話しても良かったかなと思っていますが、Web形式の説明会だと難しかったですね…

意識していたこと

面接が決まってから、2冊くらい面接対策本みたいなのを買ったんですけど1冊は半分くらい読んでもう1冊は読んでて「うっ」ってなったので読むのをやめました
なので、面接で話す内容としては、自分がどういう人間で、自分の興味のある分野はこれで、今こういうことやっているということだけちゃんと意識して話してました
まとめると、自分は、マラソン系の最適化問題・探索に興味があって、マラソン系コンテストやCodinGameに取り組んでいて、実際にこれくらいの成績を取りました、という感じです
研究に関しては、ずっとやってきていたのでその都度その都度、相手に合わせて理解してもらえるように自分の言葉で説明していました(結果的に、その辺りも評価されたみたいです)
後は、ポリシーというか自分の譲れないところとして、いわゆる面接の嘘つき大会みたいなのが嫌だったので、特に取り繕うことはせず、ありのままを見せつける感じで受けていました(もちろん、礼儀は尽くしますが、敬語が怪しかったりと結構ア)
結果的には、面接官の方と形式張らずにお話ができたかなという感じです、面接前は死ぬほど緊張するんですが、面接始まってからは結構自然体で、相手側から振られて雑談とかもしていました

感想

就活をした感想としては、面接がイメージしていた面接って感じじゃなかったですね
就活を始める前はいわゆる面接が死ぬほど嫌で就活したくねーって感じでしたが、受けてみたら相手とお話する感じでかなり楽しかったです
競プロに理解があったり、技術の知見がある人ばかりだったのでそれも良かったですね
就活する上では、やっぱり自分のやりたいこととかを言語化するのが良いのかなって思いました
企業によりますが、AtCoder緑とかなら能力的に落とされることは少なさそうで、その人のやりたいこと、性格、人柄がその会社にマッチしているかをかなり見られると思いました
なので、自分のやりたいことを軸にして気になる会社をピックアップして実際に面接でお話してみるのが一番いいかなと自分は思いました
実際に、面接でお話していく中で、自分のやりたいことがより固まったみたいなところもあったので、一次面接とかは気兼ねなく受けても良いんじゃないかなーって思ってます
落ちたらその会社と合わなかっただけなので、自分に合うところ探しましょう(落ちたらやっぱりショックだけどね…)
後、企業研究自体はあまりしていなかったんですけど、受ける企業さんはかなり調べてました
具体的には、HPの内容は全部目を通す、技術ブログがあれば、それらもざっとで良いので目を通す、後は内定貰ったところしかやってませんが決算報告書とかも読んだりして結構楽しかったです
後は、企業名でググったり事前に面接官の名前がわかっていたらググって、記事や講演の記録が残っていたらそれらにも一応目を通しておきました
役に立ったかは正直わからないんですけど、受ける企業くらいは調べておいたほうが逆質問しやすいと思いますし、理解も深まっていいかなって感じでした
逆質問はこんな感じで事前に調べておいて、気になったことや聞きたいことを多いと10個くらいストックしておいて、お話していく中で発射する弾を選ぶ感じでした(1回の面接で多くても3個くらいしか逆質問してないですが、1個以上は必ず聞いてました)

おわりに

長々とポエムにお付き合いしていただいてありがとうございます
ポエムとして楽しんでいただけたなら幸いで、就活の参考になればこの上ないです(が、多分役に立たない…)
かなり内容ぼかしているのでもし興味ある人がいたら、DMとかで聞いてくれれば答えられる範囲でお答えします
就活以外でも聞きたいことがあったらぜひご連絡ください
人生が一区切りしたので競プロとMMとCodinGameをガンガンやっていくぞ!
今年中に青なりたいね
後は就活イベントで色んな人とお話する機会が多くて、それが楽しかったので競プロerとエンカしたいなあというお気持ちが強い(今は中々難しいけど…)