接着剤の精進日記

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

AtCoder Beginner Contest 297(ABC297)

atcoder.jp

A - Double Click

$ T _ {i - 1} - T _ i \leq D $ を満たす最小の $ T _ {i + 1} $ を出力する

提出コード

void solve() {
    int N, D;
    cin >> N >> D;
    vector<int> T(N);
    REP(i, N) cin >> T[i];
    REP(i, N - 1) if (T[i + 1] - T[i] <= D) {
        cout << T[i + 1] << endl;
        return;
    }
    cout << -1 << endl;
}

B - chess960

問題文の条件を満たすがどうかを全探索で確認する

提出コード

void solve() {
    string S;
    cin >> S;
    int N = S.size();
    bool ok1 = false;
    bool ok2 = false;
    REP(x, N) REP(y, x + 1, N) {
        if (S[x] == S[y] and S[x] == 'B' and x % 2 != y % 2) ok1 = true;
    }
    REP(x, N) {
        REP(y, x + 2, N) {
            REP(z, x + 1, y) {
                if (S[x] == S[y] and S[x] == 'R' and S[z] == 'K') {
                    ok2 = true;
                }
            }
        }
    }
    cout << (ok1 and ok2 ? "Yes" : "No") << endl;
}

C - PC on the Table

左端から条件を満たすものに操作を行っていく

提出コード

void solve() {
    int H, W;
    cin >> H >> W;
    vector<string> S(H);
    REP(i, H) cin >> S[i];
    REP(i, H) {
        REP(j, W - 1) if (S[i][j] == S[i][j + 1] and S[i][j] == 'T') {
            S[i][j] = 'P';
            S[i][j + 1] = 'C';
        }
    }
    REP(i, H) cout << S[i] << endl;
}

D - Count Subtractions

ユークリッド互除法を行いながらその回数を計算する

提出コード

void solve() {
    ll A, B;
    cin >> A >> B;
    ll ans = 0;
    auto f = [&](auto &&self, ll x, ll y) -> ll {
        if (y) {
            ans += x / y;
            return self(self, y, x % y);
        }
        else {
            return x;
        }
    };
    f(f, A, B);
    cout << ans - 1 << endl;
}

E - 2xN Grid

priority_queue で操作によって得られる金額の最小値の候補を管理する
各操作では現在の最小値に対して $ A _ i $ を足した値を追加していく
このときすでにある値の場合は操作を行わないものとする

提出コード

void solve() {
    int N, K;
    cin >> N >> K;
    vector<ll> A(N);
    priority_queue<ll, vector<ll>, greater<ll>> pq;
    set<ll> st;
    REP(i, N) {
        cin >> A[i];
        st.insert(A[i]);
        pq.emplace(A[i]);
    }
    REP(_, K + 100) {
        if (pq.empty()) break;
        ll mi = pq.top();
        pq.pop();
        REP(i, N) {
            if (st.count(A[i] + mi)) continue;
            st.insert(A[i] + mi);
            pq.emplace(A[i] + mi);
        }
    }
    int k = 0;
    for (auto x : st) {
        k++;
        if (k == K) {
            cout << x << endl;
            break;
        }
    }
}

F - Minimum Bounding Box 2

縦 $ h $ 、横 $ w $ のサイズの長方形領域への $ K $ 個のマスの置き方の個数を求める
個数が求められると、この長方形領域は全部で $ (H - h + 1)(W - w + 1) $ 個あり、そのサイズは $ hw $ なのでその積がスコアとなる
よって長方形領域の選び方を全探索し、その総和を 縦が $ H $ 、横が $ W $ の長方形領域への $ K $ 個のマスの置き方の個数で割ったものが答え
各長方形領域の置き方の個数は包除原理で求めることができる
参考:D - AtCoder社の冬

提出コード

void solve() {
    ll H, W, K;
    cin >> H >> W >> K;
    mint ans = 0;
    bc.init(2020202);
    REP(h, 1, H + 1) REP(w, 1, W + 1) {
        mint sum = 0;
        REP(i, 1 << 4) {
            ll x = h;
            ll y = w;
            if (i >> 0 & 1) x--;
            if (i >> 1 & 1) x--;
            if (i >> 2 & 1) y--;
            if (i >> 3 & 1) y--;
            if (x < 0 or y < 0) continue;
            if (__builtin_popcount(i) & 1) sum -= bc.nCr(x * y, K);
            else sum += bc.nCr(x * y, K);
        }
        sum *= (h * w) * (H - h + 1) * (W - w + 1);
        ans += sum;
    }
    ans /= bc.nCr(H * W, K);
    cout << ans << endl;
}

AtCoder Beginner Contest 294(ABC294)

atcoder.jp

A - Filter

偶数の入力のみ出力

提出コード

void solve() {
    int N;
    cin >> N;
    REP(i, N) {
        int a;
        cin >> a;
        if (a % 2 == 0) cout << a << " ";
    }
    cout << endl;
}

B - ASCII Art

問題文のとおりに数値を文字列に変換する

提出コード

void solve() {
    int H, W;
    cin >> H >> W;
    REP(i, H) {
        REP(j, W) {
            int a;
            cin >> a;
            if (a == 0) cout << ".";
            else cout << char('A' + a - 1);
        }
        cout << endl;
    }
}

C - Merge Sequences

座圧と同じ操作をする
$ C_k = A_i $、$ C_k = B_j $ を満たす $ k $ を二分探索で求める

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    vector<int> A(N), B(M);
    vector<int> C;
    REP(i, N) {
        cin >> A[i];
        C.emplace_back(A[i]);
    }
    REP(i, M) {
        cin >> B[i];
        C.emplace_back(B[i]);
    }
    sort(ALL(C));
    REP(i, N) {
        int idx = lower_bound(ALL(C), A[i]) - C.begin();
        cout << idx + 1 << " ";
    }
    cout << endl;
    REP(i, M) {
        int idx = lower_bound(ALL(C), B[i]) - C.begin();
        cout << idx + 1 << " ";
    }
    cout << endl;
}

D - Bank

まだ呼ばれていない番号の集合とすでに受付に呼ばれているが受付に行っていない人の集合それぞれをsetで管理をする

提出コード

void solve() {
    int N, Q;
    cin >> N >> Q;
    set<int> st1, st2;
    REP(i, N) st1.emplace(i);
    while (Q--) {
        int q;
        cin >> q;
        if (q == 1) {
            int x = *st1.begin();
            st1.erase(x);
            st2.emplace(x);
        }
        else if (q == 2) {
            int x;
            cin >> x;
            st2.erase(x - 1);
        }
        else {
            cout << *st2.begin() + 1 << endl;
        }
    }
}

E - 2xN Grid

要素ごとにRangeSetで区間を管理する
予め $ A $ の要素を区間に追加しておく
その後 $ B $ の要素を区間に追加する
$ [l, r) $ の区間を追加したときに追加される区間の増分を $ x $ とすると、その区間には元々 $ r - l - x $ 個の要素があるのでこの総和を求めればいい

提出コード

void solve() {
    ll L, N1, N2;
    cin >> L >> N1 >> N2;
    map<ll, vector<pair<ll, ll>>> mp1, mp2;
    ll sum = 0;
    REP(i, N1) {
        ll v, l;
        cin >> v >> l;
        mp1[v].emplace_back(sum, sum + l);
        sum += l;
    }
    sum = 0;
    REP(i, N2) {
        ll v, l;
        cin >> v >> l;
        mp2[v].emplace_back(sum, sum + l);
        sum += l;
    }
    ll ans = 0;
    for (auto [k, v] : mp1) {
        RangeSet<ll> rst;
        for (auto [l, r] : v) {
            rst.insert(l, r);
        }
        if (mp2.count(k)) {
            for (auto [l, r] : mp2[k]) {
                ll x = rst.insert(l, r);
                ans += r - l - x;
            }
        }
    }
    cout << ans << endl;
}

G - Distance Queries on a Tree

クエリが木上のパスではなく、列であったとすると、1点変更、区間加算ができるセグ木を使用することで求めることができる
木に対してはHL分解を行うことで列と同様にできるのでこれをセグ木を使って求めればいい

提出コード

void solve() {
    int N;
    cin >> N;
    vector<int> v(N - 1), u(N - 1), w(N - 1);
    HLD hld(N);
    REP(i, N - 1) {
        cin >> v[i] >> u[i] >> w[i];
        --v[i], --u[i];
        hld.add_edge(v[i], u[i]);
    }
    hld.build();
    SegTree<ll> seg(
        N, [](ll a, ll b) { return a + b; }, 0);
    REP(i, N - 1) { seg.set(max(hld.vid[v[i]], hld.vid[u[i]]), w[i]); }
    seg.build();
    int Q;
    cin >> Q;
    while (Q--) {
        int q;
        cin >> q;
        if (q == 1) {
            int i;
            ll w;
            cin >> i >> w;
            --i;
            seg.update(max(hld.vid[v[i]], hld.vid[u[i]]), w);
        }
        else {
            int uu, vv;
            cin >> uu >> vv;
            --uu, --vv;
            if (uu > vv) swap(uu, vv);
            ll sum = 0;
            for (auto [l, r] : hld.for_each_edge(uu, vv)) {
                sum += seg.get(l, r);
            }
            cout << sum << endl;
        }
    }
}

AtCoder Beginner Contest 293(ABC293)

atcoder.jp

A - Swap Odd and Even

問題文の通りにswapしたものを出力

提出コード

void solve() {
    string S;
    cin >> S;
    int n = S.size();
    REP(i, n / 2) swap(S[2 * i], S[2 * i + 1]);
    cout << S << endl;
}

B - Call the ID Number

シミュレートを行い、呼ばれなかった人の番号を出力

提出コード

void solve() {
    int N;
    cin >> N;
    vector<int> A(N);
    vector<int> used(N);
    REP(i, N) {
        cin >> A[i];
        --A[i];
        if (!used[i]) used[A[i]] = 1;
    }
    vector<int> ans;
    REP(i, N) if (!used[i]) ans.emplace_back(i + 1);
    cout << ans.size() << endl;
    for (auto x : ans)
        cout << x << " ";
    cout << endl;
}

C - Make Takahashi Happy

通ったマスの整数をsetなどで管理をしながら実際にDFSなどで移動経路を全探索する

提出コード

void solve() {
    int H, W;
    cin >> H >> W;
    vector A(H, vector(W, 0));
    REP(i, H) REP(j, W) cin >> A[i][j];
    int ans = 0;
    auto dfs = [&](auto &&self, int h, int w, set<int> st) {
        if (h == H - 1 and w == W - 1) {
            ans++;
            return;
        }
        REP(d, 2) {
            int nh = h + dx[d];
            int nw = w + dy[d];
            if (nh < 0 or nh >= H or nw < 0 or nw >= W) continue;
            if (st.count(A[nh][nw])) continue;
            auto nxt_st = st;
            nxt_st.emplace(A[nh][nw]);
            self(self, nh, nw, nxt_st);
        }
    };
    set<int> st;
    st.emplace(A[0][0]);
    dfs(dfs, 0, 0, st);
    cout << ans << endl;
}

D - Tying Rope

頂点を2倍にしてUnionFindで集合を管理する
制約からある集合について辺の数と頂点の個数が一致しているときサイクルとなるので各集合ごとにこれを確認する
頂点2倍にしなくてもいいらしい

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    UnionFind uf(2 * N);
    REP(i, N) uf.unite(i, N + i);
    vector<int> a(M), c(M);
    vector<char> b(M), d(M);
    REP(i, M) {
        cin >> a[i] >> b[i] >> c[i] >> d[i];
        --a[i], --c[i];
        int na = (b[i] == 'R' ? a[i] : N + a[i]);
        int nc = (d[i] == 'R' ? c[i] : N + c[i]);
        uf.unite(na, nc);
    }
    set<int> root;
    vector<int> edge(2 * N);
    REP(i, N) {
        root.emplace(uf.root(i));
        edge[uf.root(i)]++;
    }
    REP(i, M) edge[uf.root(a[i])]++;
    int X = 0, Y = root.size();
    for (auto x : root) {
        if (edge[x] == uf.size(x)) {
            X++;
            Y--;
        }
    }
    cout << X << " " << Y << endl;
}

E - Geometric Progression

求める答えは等比数列の和となる
等比数列の和を $ f(A, X, M) $ とすると $ X = 0 $ のとき $ f(A,0,M) = 0 $ となる
$ X $ が奇数のとき、$ f(A, X, M) = 1 + Af(A,X-1,M) $ となる
また、$ X $ が偶数のとき $ f(A,2n, M) = f(A,n,M) + A^n f(A,n,M) $ となるので再帰関数と繰り返し二乗法を用いて求めることができる

提出コード

void solve() {
    ll A, X, M;
    cin >> A >> X >> M;
    cout << sum(1, A, X, M) << endl;
}

G - Triple Index

ある区間 $ [l, r] $ の答えがわかっているとする
その時、区間を $ [ l - 1, r ] $ もしくは $ [l, r + 1] $ にしたときに追加される整数 $ x $ がもともとの区間に含まれる個数を $ cnt $ とするとその答えの差分は $ _{cnt +1 } C _3 - _{cnt} C _3 $ となる
同様に区間を$ [l+1, r] $ もしくは $ [l, r-1] $ としたときの答えも差分計算できる
よって上記をMo’s algorithmを用いることで求めることができる

提出コード

void solve() {
    int N, Q;
    cin >> N >> Q;
    vector<int> A(N);
    REP(i, N) cin >> A[i];
    vector<ll> ans(Q);
    vector<ll> cnt(202020);
    ll num = 0;
    auto add = [&](int idx) {
        num -= cnt[A[idx]] * (cnt[A[idx]] - 1) * (cnt[A[idx]] - 2) / 6;
        cnt[A[idx]]++;
        num += cnt[A[idx]] * (cnt[A[idx]] - 1) * (cnt[A[idx]] - 2) / 6;
    };
    auto erase = [&](int idx) {
        num -= cnt[A[idx]] * (cnt[A[idx]] - 1) * (cnt[A[idx]] - 2) / 6;
        cnt[A[idx]]--;
        num += cnt[A[idx]] * (cnt[A[idx]] - 1) * (cnt[A[idx]] - 2) / 6;
    };
    auto out = [&](int idx) { ans[idx] = num; };
    Mo mo(N);
    REP(i, Q) {
        int l, r;
        cin >> l >> r;
        mo.add(--l, r);
    }
    mo.build(add, erase, out);
    for (auto x : ans)
        cout << x << endl;
}

AtCoder Beginner Contest 291(ABC291)

atcoder.jp

A - camel Case

大文字の判定を行う

提出コード

void solve() {
    string S;
    cin >> S;
    REP(i, S.size()) {
        if ('A' <= S[i] and S[i] <= 'Z') {
            cout << i + 1 << endl;
            return;
        }
    }
}

B - Trimmed Mean

ソートをして $ N \leq x \lt 4N $ の平均値を求める

提出コード

void solve() {
    int N;
    cin >> N;
    vector<int> X(5 * N);
    REP(i, 5 * N) cin >> X[i];
    sort(ALL(X));
    int ans = 0;
    REP(i, N, 4 * N) ans += X[i];
    printf("%.12lf\n", ans / (double)(3 * N));
}

C - LRUD Instructions 2

訪れた頂点をmapなどで持ちながらシミュレートを行い、すでに訪れたかどうかをチェックする

提出コード

void solve() {
    int N;
    cin >> N;
    string S;
    cin >> S;
    map<pair<int, int>, int> mp;
    int cur_x = 0, cur_y = 0;
    mp[{cur_x, cur_y}] = 1;
    for (char c : S) {
        if (c == 'U') {
            cur_y++;
        }
        else if (c == 'D') {
            cur_y--;
        }
        else if (c == 'R') cur_x++;
        else if (c == 'L') cur_x--;
        if (mp.count({cur_x, cur_y})) {
            cout << "Yes" << endl;
            return;
        }
        mp[{cur_x, cur_y}] = 1;
    }
    cout << "No" << endl;
}

D - Flip Cards

$ i $ 番目のものを決めるときには $ i - 1 $ 番目のものを表裏どちらの状態にしたかのみに依存するので以下のDPで求めればいい
$ dp[i][j] := i $ 番目のものまで見たとき、$ i - 1 $ 番目の状態が $ j $ のときの場合の数

提出コード

void solve() {
    int N;
    cin >> N;
    vector<vector<int>> num(2, vector<int>(N));
    REP(i, N) cin >> num[0][i] >> num[1][i];
    vector dp(N + 1, vector<mint>(2, 0));
    dp[0][0] = 1;
    dp[0][1] = 1;
    REP(i, 1, N) {
        REP(j, 2) {
            REP(k, 2) {
                if (num[j][i - 1] != num[k][i]) dp[i][k] += dp[i - 1][j];
            }
        }
    }
    cout << dp[N - 1][0] + dp[N - 1][1] << endl;
}

E - Find Permutation

与えられた条件を有向グラフとして考える
このグラフ上で長さが $ N - 1 $ のパスが存在すれば条件を満たすのでこれを判定する

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    vector<int> X(M), Y(M);
    vector<vector<int>> g(N);
    REP(i, M) {
        cin >> X[i] >> Y[i];
        --X[i], --Y[i];
        g[X[i]].emplace_back(Y[i]);
    }
    auto res = topologicalSort(g);
    vector<int> dist(N, -1);
    dist[res[0]] = 0;
    for (auto x : res) {
        for (auto nv : g[x])
            chmax(dist[nv], dist[x] + 1);
    }
    int ma = *max_element(ALL(dist));
    dump(dist);
    if (ma != N - 1) cout << "No" << endl;
    else {
        cout << "Yes" << endl;
        vector<int> ans(N);
        REP(i, N) ans[res[i]] = i + 1;
        for (auto x : ans)
            cout << x << " ";
        cout << endl;
    }
}

F - Teleporter and Closed off

頂点 $ i $ を使わずに移動可能かどうかは 頂点 $ i $ を飛び越すような辺が存在すればよい
そのような辺は制約より、 $ i $ の前後 $ M $ 個の頂点に絞られるのでこれをすべて調べれば良い
頂点 $ 1 $ からの最短距離を $ d_1 $ 、$ N $ からの最短経路を $ d_2 $ とし、頂点 $ j $ から $ k $ への辺が存在するとき、そのコストは $ d_{1,j} + d_{N, k} + 1 $ となるのでその最小を答えればいい

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    vector<string> S(N);
    Dijkstra<int> d1(N, INF), d2(N, INF);
    REP(i, N) {
        cin >> S[i];
        REP(j, M) if (S[i][j] == '1') {
            d1.make_edge(i, i + j + 1, 1);
            d2.make_edge(i + j + 1, i, 1);
        }
    }
    d1.solve(0);
    d2.solve(N - 1);
    dump(d1.cost);
    dump(d2.cost);
    REP(i, 1, N - 1) {
        int ans = INF;
        REP(j, max(0, i - (M - 1)), i) {
            REP(k, i + 1, min(N, i + 1 + (M - 1))) {
                if (k - j - 1 < M and S[j][k - j - 1] == '1') chmin(ans, d1.cost[j] + d2.cost[k] + 1);
            }
        }
        cout << (ans == INF ? -1 : ans) << " ";
    }
    cout << endl;
}

AtCoder Beginner Contest 289(ABC289)

atcoder.jp

A - flip

1文字ずつ見ていき、各文字を反転させたものを出力する

提出コード

void solve() {
    string S;
    cin >> S;
    for (char c : S) {
        if (c == '1') cout << 0;
        else cout << 1;
    }
    cout << endl;
}

B - レ

連結成分をUnionFindで管理する
連結成分に含まれる頂点を降順ソートし、出力する

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    UnionFind uf(N);
    REP(i, M) {
        int a;
        cin >> a;
        --a;
        uf.unite(a, a + 1);
    }
    map<int, vector<int>> mp;
    vector<int> used(N);
    REP(i, N) mp[uf.root(i)].emplace_back(i);
    REP(i, N) if (!used[i]) {
        auto v = mp[uf.root(i)];
        sort(ALL(v));
        reverse(ALL(v));
        for (auto x : v) {
            cout << x + 1 << " ";
            used[x] = 1;
        }
    }
    cout << endl;
}

C - Coverage

bit全探索で条件を満たすものを数える

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    vector<vector<int>> a(M);
    REP(i, M) {
        int C;
        cin >> C;
        REP(j, C) {
            int num;
            cin >> num;
            --num;
            a[i].emplace_back(num);
        }
    }
    int ans = 0;
    REP(i, 1 << M) {
        int bits = 0;
        REP(j, M) if (i >> j & 1) {
            REP(k, a[j].size()) { bits |= (1 << a[j][k]); }
        }
        if (__builtin_popcount(bits) == N) ans++;
    }
    cout << ans << endl;
}

D - Step Up Robot

$ dp[i] := i $ 段目にたどり着けるかどうかをDPで求める

提出コード

void solve() {
    int N;
    cin >> N;
    vector<int> A(N);
    REP(i, N) cin >> A[i];
    int M;
    cin >> M;
    vector<int> B(101010);
    REP(i, M) {
        int b;
        cin >> b;
        B[b] = 1;
    }
    int X;
    cin >> X;
    vector<int> dp(101010);
    dp[0] = 1;
    REP(i, 101010) if (dp[i] and !B[i]) {
        REP(j, N) {
            int nxt = i + A[j];
            if (nxt < 101010 and !B[nxt]) dp[nxt] = 1;
        }
    }
    cout << (dp[X] ? "Yes" : "No") << endl;
}

E - Swap Places

高橋くんが頂点 $ i $、青木くんが頂点 $ j $ にいる状態を一つの頂点とみなす
その頂点上でBFSを行うことで行動回数の最小値を求めることができる

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    vector<int> C(N);
    REP(i, N) cin >> C[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 dist(N, vector(N, INF));
    dist[0][N - 1] = 0;
    queue<P> q;
    q.emplace(0, N - 1);
    while (!q.empty()) {
        auto [u, v] = q.front();
        q.pop();
        for (auto nu : g[u]) {
            for (auto nv : g[v]) {
                if (C[nu] == C[nv]) continue;
                if (dist[nu][nv] < INF) continue;
                dist[nu][nv] = dist[u][v] + 1;
                q.emplace(nu, nv);
            }
        }
    }
    cout << (dist[N - 1][0] == INF ? -1 : dist[N - 1][0]) << endl;
}