接着剤の精進日記

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

AtCoder Beginner Contest 312(ABC312)

atcoder.jp

A - Chord

問題文のとおりに判定する

提出コード

void solve() {
    string S;
    cin >> S;
    vector<string> T = {"ACE", "BDF", "CEG", "DFA", "EGB", "FAC", "GBD"};
    for (auto t : T) {
        if (S == t) {
            cout << "Yes" << endl;
            return;
        }
    }
    cout << "No" << endl;
}

B - TaK Code

サンプルにTak Codeがあるのでそれを利用する
左上の点の位置を全探索し、#. の点の位置全て一致するかを判定する

###.?????
###.?????
###.?????
....?????
?????????
?????....
?????.###
?????.###
?????.###

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    vector<string> S(N);
    REP(i, N) cin >> S[i];
    vector<string> task = {
        "###.?????",
        "###.?????",
        "###.?????",
        "....?????",
        "?????????",
        "?????....",
        "?????.###",
        "?????.###",
        "?????.###",
    };
    REP(i, N - 9 + 1) REP(j, M - 9 + 1) {
        int cnt = 0;
        REP(h, 9) REP(w, 9) {
            if (task[h][w] == '?') cnt++;
            else if (task[h][w] == S[i + h][j + w]) cnt++;
        }
        if (cnt == 9 * 9) {
            cout << i + 1 << " " << j + 1 << endl;
        }
    }
}

C - Invisible Hand

$ X $ 円のときに条件を満たすかどうかを判定できるのでこれを二分探索で求める

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    vector<int> A(N), B(M);
    REP(i, N) cin >> A[i];
    REP(i, M) cin >> B[i];
    sort(ALL(A));
    sort(ALL(B));
    ll l = 0, r = 2 * INF;
    while (r - l > 1) {
        ll m = (l + r) >> 1;
        ll cnt_a = 0;
        ll cnt_b = 0;
        REP(i, N) if (A[i] <= m) cnt_a++;
        REP(i, M) if (B[i] >= m) cnt_b++;
        if (cnt_a >= cnt_b) r = m;
        else l = m;
    }
    cout << r << endl;
}

D - Count Bracket Sequences

括弧列は ( のとき +1 、 ) のとき -1 としたときの累積和が途中で負にならず、合計が0となるものであるので
$ dp[i][j] := i $ 番目の文字まで見たときの累積和が $ j $ であるときの場合の数としてDPを行えば良い

提出コード

void solve() {
    string S;
    cin >> S;
    const int N = S.size();
    vector<mint> dp(N + 110);
    dp[0] = 1;
    REP(i, N) {
        vector<mint> nxt(N + 10);
        REP(j, N + 1) {
            if (S[i] == '(') {
                nxt[j + 1] += dp[j];
            }
            else if (S[i] == ')') {
                if (j > 0) nxt[j - 1] += dp[j];
            }
            else {
                nxt[j + 1] += dp[j];
                if (j > 0) nxt[j - 1] += dp[j];
            }
        }
        swap(dp, nxt);
    }
    cout << dp[0] << endl;
}

E - Tangency of Cuboids

それぞれの立方体が重なることはないので $ 1 \times 1 \times 1 $ の立方体は高々1つの立方体に属することになる
また、各立方体は上下左右前後の6方向に対して別の立方体が属する $ 1 \times 1 \times 1 $ の立方体が存在すれば面で接することになる
立方体の数は制約から最大で $ 10 ^ 6 $ 程度であるので全探索をすれば良い

提出コード

void solve() {
    int N;
    cin >> N;
    vector<int> X1(N), Y1(N), Z1(N), X2(N), Y2(N), Z2(N);
    vector cube(111, vector(111, vector(111, -1)));
    REP(i, N) {
        cin >> X1[i] >> Y1[i] >> Z1[i] >> X2[i] >> Y2[i] >> Z2[i];
        REP(x, X1[i], X2[i]) REP(y, Y1[i], Y2[i]) REP(z, Z1[i], Z2[i]) cube[x][y][z] = i;
    }
    vector<set<int>> st(N);
    REP(x, 101) REP(y, 101) REP(z, 101) if (cube[x][y][z] != -1) {
        if (cube[x + 1][y][z] != -1 and cube[x + 1][y][z] != cube[x][y][z]) {
            st[cube[x + 1][y][z]].emplace(cube[x][y][z]);
            st[cube[x][y][z]].emplace(cube[x + 1][y][z]);
        }
        if (cube[x][y + 1][z] != -1 and cube[x][y + 1][z] != cube[x][y][z]) {
            st[cube[x][y + 1][z]].emplace(cube[x][y][z]);
            st[cube[x][y][z]].emplace(cube[x][y + 1][z]);
        }
        if (cube[x][y][z + 1] != -1 and cube[x][y][z + 1] != cube[x][y][z]) {
            st[cube[x][y][z + 1]].emplace(cube[x][y][z]);
            st[cube[x][y][z]].emplace(cube[x][y][z + 1]);
        }
    }
    REP(i, N) cout << st[i].size() << endl;
}

F - Cans and Openers

$ T_i = 2 $ のものを使うときは $ X_i $ が大きいものから順に使っていけばいい
また、$ T_i = 0 $ のものははじめから全て集合に追加されているとし、$ T_i = 2 $ のものを選んだときの $ X_i $ の合計だけ $ T_i = 1 $ の要素を降順に集合に追加していくと考える
そうすると、今ある集合から降順 $ k $ 個の総和を求める事ができれば良い
これはBITやpriority_queueを用いれば高速に求めることができるので、$ T_i = 2 $ のものを $ i $ 個選び、$ X_i $ 個だけ $ T_i = 1 $ のものが追加された集合の降順 $ M - i $ 個の総和をそれぞれ求め、その最大を答えれば良い

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    vector<int> T(N), X(N);
    vector<vector<int>> TX(3);
    MaximumSum<ll> sum(M);
    REP(i, N) {
        cin >> T[i] >> X[i];
        TX[T[i]].push_back(X[i]);
    }
    REP(i, M) sum.insert(0);
    for (auto x : TX[0]) {
        sum.insert(x);
    }
    priority_queue<int> pq;
    for (auto x : TX[1]) {
        pq.push(x);
    }
    sort(ALL(TX[2]));
    reverse(ALL(TX[2]));
    ll ans = sum.query();
    int used = 0;
    for (auto x : TX[2]) {
        while (x > 0 && !pq.empty()) {
            int y = pq.top();
            pq.pop();
            sum.insert(y);
            x--;
            dump(y);
        }
        used++;
        if (M - used <= 0) break;
        sum.set_k(M - used);
        chmax(ans, sum.query());
    }
    cout << ans << endl;
}

AtCoder Beginner Contest 308(ABC308)

atcoder.jp

A - New Scheme

問題文の条件を判定する

提出コード

void solve() {
    vector<int> S(8);
    REP(i, 8) cin >> S[i];
    bool ok = true;
    REP(i, 8) if (!(100 <= S[i] and S[i] <= 675)) ok = false;
    REP(i, 8) if (S[i] % 25 != 0) ok = false;
    REP(i, 7) if (S[i] > S[i + 1]) ok = false;
    cout << (ok ? "Yes" : "No") << endl;
}

B - Default Price

mapなどで色に対応する値段を管理する

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    vector<string> C(N), D(M);
    vector<int> P(M + 1);
    REP(i, N) cin >> C[i];
    REP(i, M) cin >> D[i];
    REP(i, M + 1) cin >> P[i];
    map<string, int> mp;
    REP(i, M) mp[D[i]] = P[i + 1];
    ll ans = 0;
    REP(i, N) {
        if (mp.count(C[i])) ans += mp[C[i]];
        else ans += P[0];
    }
    cout << ans << endl;
}

C - Standings

$ \frac{A_i}{A_i+B_i} \lt \frac{A_j} {A_j+B_j} $ の分母を払って $ A_i(A_j+B_j) \lt A_j(A_i+B_i) $ を比較関数にしてソートを行えば良い

提出コード

void solve() {
    int N;
    cin >> N;
    vector<ll> A(N), B(N);
    REP(i, N) cin >> A[i] >> B[i];
    vector<int> idx(N);
    iota(ALL(idx), 0);
    sort(ALL(idx), [&](int i, int j) {
        return A[i] * (A[j] + B[j]) == A[j] * (A[i] + B[i]) ? i < j : A[i] * (A[j] + B[j]) > A[j] * (A[i] + B[i]);
    });
    for (auto i : idx)
        cout << i + 1 << " ";
    cout << endl;
}

D - Snuke Maze

$ dist[i][h][w] := $ マス $ (h, w) $ にいて、その時の距離が $ i \pmod{5} $ であるときの最短距離としてBFSなどでこれを求める

提出コード

void solve() {
    int H, W;
    cin >> H >> W;
    vector<string> S(H);
    string snuke = "snuke";
    REP(i, H) cin >> S[i];
    vector dist(5, vector(H, vector(W, INF)));
    if (S[0][0] != 's') {
        cout << "No" << endl;
        return;
    }
    dist[0][0][0] = 0;
    using T = tuple<int, int, int>;
    queue<T> q;
    q.emplace(0, 0, 0);
    while (!q.empty()) {
        auto [d, h, w] = q.front();
        q.pop();
        REP(dd, 4) {
            int nh = h + dy[dd];
            int nw = w + dx[dd];
            if (nh < 0 || nh >= H || nw < 0 || nw >= W) continue;
            int nxt_d = (d + 1) % 5;
            if (S[nh][nw] == snuke[nxt_d]) {
                if (dist[nxt_d][nh][nw] != INF) continue;
                q.emplace(nxt_d, nh, nw);
                dist[nxt_d][nh][nw] = dist[d][h][w] + 1;
            }
        }
    }
    bool ok = false;
    REP(i, 5) if (dist[i][H - 1][W - 1] < INF) ok = true;
    cout << (ok ? "Yes" : "No") << endl;
}

E - MEX

$ dp[i][j][k] := i $ 番目まで見て、MEXの $ j $ 文字目まで決めたときに選んだ集合が $ k $ であるときの場合の数としてDPを行う
最後に $ dp[N][3][k] \times MEX(k) $ の総和を求めてそれを出力する

提出コード

void solve() {
    int N;
    cin >> N;
    vector<int> A(N);
    REP(i, N) cin >> A[i];
    string S;
    cin >> S;
    vector dp(N + 1, vector(4, vector(1 << 3, 0LL)));
    dp[0][0][0] = 1;
    string MEX = "MEX";
    REP(i, N) {
        REP(j, 4) {
            REP(k, 1 << 3) {
                dp[i + 1][j][k] += dp[i][j][k];
                if (j < 3 and S[i] == MEX[j]) {
                    dp[i + 1][j + 1][k | (1 << A[i])] += dp[i][j][k];
                }
            }
        }
    }
    ll ans = 0;
    REP(i, 1 << 3) {
        REP(j, 4) {
            if (!(i >> j & 1)) {
                {
                    ans += dp[N][3][i] * (ll)j;
                    break;
                }
            }
        }
    }
    cout << ans << endl;
}

F - Vouchers

買う値段を最小化したいので、$ D_i $ の大きい順に使っていくことを考える
このとき、 $ L_i $ を満たすギリギリのものを選びたいのでmultisetなどで集合を管理し、lower_boundでその値を求める

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    vector<int> P(N), L(M), D(M);
    REP(i, N) cin >> P[i];
    REP(i, M) cin >> L[i];
    REP(i, M) cin >> D[i];
    sort(ALL(P));
    reverse(ALL(P));
    multiset<int> mst;
    priority_queue<pair<int, int>> pq;
    REP(i, N) mst.insert(P[i]);
    REP(i, M) pq.emplace(D[i], L[i]);
    ll ans = 0;
    while (!pq.empty()) {
        auto [d, l] = pq.top();
        pq.pop();
        auto it = mst.lower_bound(l);
        if (it == mst.end()) continue;
        ans += *it - d;
        mst.erase(it);
    }
    for (auto x : mst)
        ans += x;
    cout << ans << endl;
}

AtCoder Beginner Contest 306(ABC306)

atcoder.jp

A - Echo

$ S_i $ を2個ずつ出力する

提出コード

void solve() {
    int N;
    string S;
    cin >> N >> S;
    REP(i, N) cout << S[i] << S[i];
    cout << endl;
}

B - Base 2

問題文の通りに計算するだけだが、long long ではオーバーフローするので多倍長整数unsigned long long を使う

提出コード

void solve() {
    unsigned ll ans = 0;
    REP(i, 64) {
        int a;
        cin >> a;
        if (a) ans += 1uLL << i;
    }
    cout << ans << endl;
}

C - Centers

$ A $ の2番目の要素でソートを行う

提出コード

void solve() {
    int N;
    cin >> N;
    vector<vector<int>> A(N);
    REP(i, 3 * N) {
        int a;
        cin >> a;
        --a;
        A[a].push_back(i);
    }
    vector<int> idx(N);
    iota(ALL(idx), 0);
    sort(ALL(idx), [&](int a, int b) { return A[a][1] < A[b][1]; });
    REP(i, N) cout << idx[i] + 1 << " \n"[i == N - 1];
}

D - Poisonous Full-Course

$ dp[i][j] := i $ 番目までの料理まで選択したときに、高橋くんの状態が $ j $ (お腹を壊しているかどうか)のときに得られた美味しさの和の最大としてDPを行う

提出コード

void solve() {
    int N;
    cin >> N;
    vector<int> X(N), Y(N);
    REP(i, N) cin >> X[i] >> Y[i];
    vector dp(2, -LINF);
    dp[0] = 0;
    REP(i, N) {
        vector ndp(2, -LINF);
        chmax(ndp[0], dp[0]);
        chmax(ndp[1], dp[1]);
        if (X[i] == 0) {
            chmax(ndp[0], dp[0] + Y[i]);
            chmax(ndp[0], dp[1] + Y[i]);
        }
        else {
            chmax(ndp[1], dp[0] + Y[i]);
        }
        swap(dp, ndp);
    }
    cout << max(dp[0], dp[1]) << endl;
}

E - Best Performances

BITでk-th elementの取得と、区間和を求めることで解くことができる
降順に $ k $ 番目の値は昇順に $ N - K $ 番目の値になるのでこの値を取得し、それ以降の区間和を求めればいい

提出コード

void solve() {
    int N, K, Q;
    cin >> N >> K >> Q;
    Compress<int> cmp;
    vector<int> A(N);
    vector<int> X(Q), Y(Q);
    REP(i, Q) {
        cin >> X[i] >> Y[i];
        cmp.add(Y[i]);
        --X[i];
    }
    cmp.add(0);
    cmp.add(2 * INF);
    cmp.build();
    int sz = cmp.size();
    BIT<ll> bit(sz), bit_sum(sz);
    bit.add(0, N);
    ll ans = 0;

    REP(i, Q) {
        int pre = A[X[i]];
        bit.add(cmp.get(pre), -1);
        bit_sum.add(cmp.get(pre), -pre);
        bit.add(cmp.get(Y[i]), 1);
        bit_sum.add(cmp.get(Y[i]), Y[i]);

        int k = bit.kth_element(N - K - 1) - 1;
        ll ans = 0;
        ans += bit_sum.sum(k + 1, sz);
        ans += max(0LL, (K - bit.sum(k + 1, sz))) * cmp[k];
        cout << ans << endl;
        A[X[i]] = Y[i];
    }
}

F - Merge Sets

各$ A _ i $ を昇順に並べ替える操作を予め行っておく
$ A _ {i , j } $ が $ A, B $ の集合の中で答えに寄与する分は、$ A $ の中で 自分より小さいものの個数と、$ B $ の中で自分より小さいものの個数になる
前者は $ j (N - i) $ として求めることができ、後者はBITなどで求めることができる
よって後者を実際に求め最後に前者の値を足した総和が答え

提出コード

void solve() {
    ll N, M;
    cin >> N >> M;
    vector<vector<int>> A(N, vector<int>(M, 0));
    Compress<int> cmp;
    REP(i, N) {
        REP(j, M) {
            cin >> A[i][j];
            cmp.add(A[i][j]);
        }
        sort(ALL(A[i]));
    }
    cmp.build();
    int sz = cmp.size();
    ll ans = 0;
    BIT<int> bit(sz);
    REP(i, N) REP(j, M) bit.add(cmp.get(A[i][j]), 1);
    REP(i, N) {
        REP(j, M) bit.add(cmp.get(A[i][j]), -1);
        REP(j, M) {
            ans += (j + 1) * (N - i - 1);
            ans += bit.sum(0, cmp.get(A[i][j]));
        }
    }
    cout << ans << endl;
}

接着剤の「CODINGAME SPRING CHALLENGE 2023」 参加記

はじめに

CodinGameのコンテスト(CODINGAME SPRING CHALLENGE 2023)に参加しました
結果は世界82/5290位(Legend)、日本20/309位でした
🐜さん観察日記

ルール

例のごとくツカモさんの要約記事です(ありがとうございます)
tsukammo.hatenablog.com

やったこと

概要

方針:最初にある程度卵を集めて🐜を増やす、その後は距離順に最小シュタイナー木っぽくリソースマスをビーコンで繋いでいく
ルールベース + ダイクストラでの経路決め

BEACON

ビーコンの強さは1で固定
ダイクストラで求めた経路を復元し、経路上のマスにBEACONを設置

ダイクストラ

自分のベースとすでに設置したビーコンを始点としてダイクストラをする
(そのマスの蟻の総数 + 1)の逆数をコストとして最も近いリソースマスを貪欲に選ぶ
お気持ちとしては蟻が全くいない方向にビーコンを置くと移動に時間がかかるのですでに蟻がいる近くのリソースマスを狙うようにしたいため
選ぶリソースマスの総数は以下のルールベースで選択
現在の盤面に存在する卵のマスの個数を $ EggCells $、クリスタルのマスの個数を $ CrystalCells $ とする

  1. 取得した🐜の卵の総数が初期配置の🐜の卵の総数の15%未満の場合、最大 $ \lfloor \frac{EggCells}{2} \rfloor + 1 $ まで卵のマスを選択する
  2. 取得した🐜の卵の総数が初期配置の🐜の卵の総数の30%未満の場合、最大 $ \lfloor \frac{EggCells + CrystalCells}{2} \rfloor + 1 $ までマスを選択する、ただし卵のマスがある場合は卵のマスを優先して選び、残りをクリスタルのマスを選ぶ
  3. それ以外の場合、残っているマスの中から距離順に貪欲に選ぶ

マスを選ぶ際に相手始点でもダイクストラで各マスへの距離を求めておき、マス $ i $ の距離が $ OpponentDist[i] + 1 < MeDist[i] $ の場合は選択しない
境界付近のマスを除いて相手の方が近いマスは基本的には狙わないほうが良く、自分が取るべき個数(初期リソースマスの半分)+ 1個(中心に配置されているような自分と相手が争うマス)を取ることができれば勝てるため

やりたかったこと

・nターン後のスコア最大化(探索)
・ビーコンを上手く置いて🐜を賢く動かす(諦めた)

知見

焼きなまし(山登り)でビーコンの配置を最適化

感想

🐜の挙動を上手く動かすのが難しくてコンテスト期間中ずっと考えていたけど結局しっくりくる方法が思いつかず諦めた
シミュレートも書いたけど重すぎて結局使わなかったんだけどいい感じにシミュレートする方法あるのかな、気になる
とにかく何もわからなかったのでレジェンド行けて満足

おまけ(ブロンズ到達RTA

やったこと

リソースのあるマスを全部LINEコマンドで選ぶ(サンプルコード+数行)

AtCoder Beginner Contest 301(ABC301)

atcoder.jp

A - Overall Winner

TA の個数を数える
個数が同じ場合、最後に数えたものが負け

提出コード

void solve() {
    int N;
    string S;
    cin >> N >> S;
    bool f = false;
    int a = 0, t = 0;
    REP(i, N) {
        if (S[i] == 'T') {
            t++;
            f = true;
        }
        else {
            a++;
            f = false;
        }
    }
    if (a > t) cout << "A" << endl;
    else if (a < t) cout << "T" << endl;
    else {
        if (f) cout << "A" << endl;
        else cout << "T" << endl;
    }
}

B - Fill the Gaps

問題文の条件のとおりに出力をする

提出コード

void solve() {
    int N;
    cin >> N;
    vector<int> A(N);
    REP(i, N) cin >> A[i];
    cout << A[0] << " ";
    REP(i, 1, N) {
        if (abs(A[i] - A[i - 1]) == 1) cout << A[i] << " ";
        else if (A[i] > A[i - 1]) {
            REP(j, A[i - 1] + 1, A[i] + 1) cout << j << " ";
        }
        else if (A[i] < A[i - 1]) {
            for (int j = A[i - 1] - 1; j >= A[i]; j--)
                cout << j << " ";
        }
    }
    cout << endl;
}

C - AtCoder Cards

各文字ごとに文字数を数え、STで文字数が同じかどうかを比較していく
このとき @a, t, c, o, d, e, rに置き換えられるので足りない分は置き換えてしまっていい
STで文字数を同じにできない文字がある場合はNo、それ以外はYesとなる

提出コード

void solve() {
    string S, T;
    cin >> S >> T;
    vector<int> s(27), t(27);
    for (char c : S) {
        if (c == '@') s[26]++;
        else s[c - 'a']++;
    }
    for (char c : T) {
        if (c == '@') t[26]++;
        else t[c - 'a']++;
    }
    set<char> st = {'a', 't', 'c', 'o', 'd', 'e', 'r'};
    REP(i, 26) {
        if (s[i] == t[i]) continue;
        else if (!st.count(char(i + 'a'))) {
            cout << "No" << endl;
            return;
        }
        else if (s[i] > t[i]) {
            if (s[i] - t[i] > t[26]) {
                cout << "No" << endl;
                return;
            }
            else {
                t[26] -= s[i] - t[i];
            }
        }
        else if (s[i] < t[i]) {
            if (t[i] - s[i] > s[26]) {
                cout << "No" << endl;
                return;
            }
            else {
                s[26] -= t[i] - s[i];
            }
        }
    }
    cout << "Yes" << endl;
}

D - Bitmask

?0とした場合の2進数整数を予め求めておく
その後、上位bitから順に見ていき?1としたときの2進数整数が $ N $ 以下の場合、そのbitは1としてしまって良い

提出コード

void solve() {
    string S;
    ll N;
    cin >> S >> N;
    ll cur = 0;
    reverse(ALL(S));
    REP(i, S.size()) if (S[i] == '1') cur += 1LL << i;
    if (cur > N) {
        cout << -1 << endl;
        return;
    }
    for (int i = (int)S.size() - 1; i >= 0; i--)
        if (S[i] == '?' and cur + (1LL << i) <= N) cur += 1LL << i;
    cout << cur << endl;
}

E - Pac-Takahashi

S, G, o のみに注目すると頂点数は高々20となり巡回セールスマン問題のようにbit DPで求めることができる
そのため、各頂点間の距離を予めBFSなどで求めておくことで以下のDPで答えを求めることができる
$ dp[i][j] := $ 最後に訪れた頂点が $ i $ で、訪れた頂点集合が $ j $ としたときの移動回数の最小
求めたい答えは、最後にGを訪れたときに移動回数が $ T $ 以下であるときの頂点集合に含まれる oの数の最大であるので条件を満たすもののうち、頂点集合に含まれる oの数の最大を求めればいい

提出コード

void solve() {
    int H, W, T;
    cin >> H >> W >> T;
    vector<string> s(H);
    REP(i, H) cin >> s[i];
    vector<pair<int, int>> snack;
    int sh, sw, gh, gw;
    REP(i, H) REP(j, W) {
        if (s[i][j] == 'S') sh = i, sw = j;
        else if (s[i][j] == 'G') gh = i, gw = j;
    }
    snack.emplace_back(sh, sw);
    snack.emplace_back(gh, gw);
    REP(i, H) REP(j, W) {
        if (s[i][j] == 'o') snack.emplace_back(i, j);
    }
    int n = snack.size();
    vector<vector<int>> dist(n, vector<int>(n, INF));
    REP(i, n) {
        auto [h, w] = snack[i];
        vector<vector<int>> d(H, vector<int>(W, INF));
        d[h][w] = 0;
        queue<pair<int, int>> q;
        q.emplace(h, w);
        while (!q.empty()) {
            auto [cur_h, cur_w] = q.front();
            q.pop();
            REP(dir, 4) {
                int nh = cur_h + dx[dir];
                int nw = cur_w + dy[dir];
                if (nh < 0 || nh >= H || nw < 0 || nw >= W) continue;
                if (s[nh][nw] == '#') continue;
                if (d[nh][nw] < INF) continue;
                d[nh][nw] = d[cur_h][cur_w] + 1;
                q.emplace(nh, nw);
            }
        }
        REP(j, n) dist[i][j] = d[snack[j].first][snack[j].second];
    }
    vector dp(n, vector<int>(1 << n, INF));
    dp[0][0] = 0;
    dp[0][1] = 0;  // s = 0, g=1
    REP(i, 1, (1 << n) + 1) {
        REP(j, n) if (i >> j & 1) {
            REP(k, n) {
                int cost = dp[j][i ^ (1 << j)] + dist[j][k];
                chmin(dp[k][i], cost);
            }
        }
    }
    int ma = -1;
    REP(i, 1 << n) if ((i & 1) and (i >> 1 & 1) and dp[1][i] <= T) { chmax(ma, __builtin_popcount(i ^ 1 ^ 2)); }
    cout << ma << endl;
}