接着剤の精進日記

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

AtCoder Beginner Contest 197(ABC197)

atcoder.jp

A - Rotate

$ S[1]S[2]S[0] $ を出力
提出コード

void solve(){
    string S;
    cin >> S;
    cout << S[1] << S[2] << S[0] << endl;
}

B - Visibility

マス $ (X, Y) $ から上下左右に#にぶつかるまでのマスの個数を数える
提出コード

void solve(){
    int H, W, X, Y;
    cin >> H >> W >> X >> Y;
    X--,Y--;
    vector<string> S(H);
    REP(i,H) cin >> S[i];
    int ans = 1;
    for(int i=X+1;i<H;i++){
        if(S[i][Y] == '#') break;
        ans++;
    }
    for(int i=X-1;i>=0;i--){
        if(S[i][Y] == '#') break;
        ans++;
    }
    for(int i=Y+1;i<W;i++){
        if(S[X][i] == '#') break;
        ans++;
    }
    for(int i=Y-1;i>=0;i--){
        if(S[X][i] == '#') break;
        ans++;
    }
    cout << ans << endl;
}

C - ORXOR

どの箇所に仕切りを入れるかどうかをbit全探索をする
提出コード

void solve(){
    int N;
    cin >> N;
    vector<ll> A(N);
    REP(i,N) cin >> A[i];
    ll ans = LINF;
    REP(i,1<<N){
        ll cur = 0;
        ll sum = 0;
        REP(j,N){
            cur |= A[j];
            if(i >> j & 1){
                sum ^= cur;
                cur = 0;
            }
        }
        sum ^= cur;
        chmin(ans, sum);
    }
    cout << ans << endl;
}

D - Opposite

正 $ N $ 角形の中心 $ (x, y) $を考える
$ (x, y) = \left(\frac{x_0 + x_{\frac{N}{2}}}{2}, \frac{y_0 + y_{\frac{N}{2}}}{2} \right)$ となるので、$ (x_0 - x, y_0 - y) $ として(x, y)を原点とした時の $ (x_0, y_0) $ を考える
この時、$ (x_1, y_1) $ は $ (x_0, y_0) $ を $ \theta = \frac{2\pi}{N} $ だけ原点を中心に回転させたものとなる
あとはこの座標を求めたあとにもとの座標に戻して上げればいい
提出コード

void solve(){
    double N;
    cin >> N;
    double x0, y0, x2, y2;
    cin >> x0 >> y0 >> x2 >> y2;
    double x = (x2 + x0) / 2.0;
    double y = (y2 + y0) / 2.0;
    double x1 = x + (x0 - x)*cos(2.0*PI/N) - (y0 - y)*sin(2.0*PI/N);
    double y1 = y + (x0 - x)*sin(2.0*PI/N) + (y0 - y)*cos(2.0*PI/N);
    printf("%.12lf %.12lf\n", x1, y1);
}

E - Traveler

色 $ i $ のボールをすべて取り終わったときにどこにいるかを考えると色 $ i $ のボールが置いてある最小の座標と最大の座標のどちらかであるのでその2つのみを考えればいい
そのため次のようなDPを考えればいい
$ dp[i][j] := $ 色 $ i $ のボールをすべて取り終わったときに一番左端 $ (j=0) $ にいる、一番右端にいる$ (j=1) $ ときの最小のコストとしてDPをする
提出コード

void solve(){
    ll N;
    cin >> N;
    vector<ll> X(N), C(N);
    map<ll, vector<ll>> mp;
    Compress<ll> cmp;
    cmp.add(0);
    REP(i,N){
        cin >> X[i] >> C[i];
        cmp.add(C[i]);
    }
    cmp.build();
    REP(i,N) mp[cmp.get(C[i])].emplace_back(X[i]);
    for(auto &[k, v] : mp){
        sort(v.begin(), v.end());
    }
    mp[0].emplace_back(0);
    auto f = [&](ll a, ll b) -> ll{
        return abs(a - b);
    };
    int sz = mp.size();
    vector<vector<ll>> dp(sz+1, vector<ll>(2, LINF));
    dp[0][0] = dp[0][1] = 0;
    for(int i=1;i<sz;i++){
        auto curMi = mp[i].front();
        auto curMa = mp[i].back();
        auto preMi = mp[i-1].front();
        auto preMa = mp[i-1].back();
        ll dist = f(curMa, curMi);
        chmin(dp[i][0], dp[i-1][0] + f(preMi, curMa) + dist); // mi -> ma -> mi
        chmin(dp[i][0], dp[i-1][1] + f(preMa, curMa) + dist); // ma -> ma -> mi
        chmin(dp[i][1], dp[i-1][0] + f(preMi, curMi) + dist); // mi -> mi -> ma
        chmin(dp[i][1], dp[i-1][1] + f(preMa, curMi) + dist); // ma -> mi -> ma
    }
    cout << min(dp[sz-1][0] + abs(mp[sz-1].front()), dp[sz-1][1]+abs(mp[sz-1].back())) << endl;
}

F - Construct a Palindrome

頂点 $ 1 $ と 頂点 $ N $ に置かれた2つのコマが同じ文字の辺を通って合流もしくは隣接した頂点にたどり着くことを考える
$ dist[a][b] := a $ と $ b $ にそれぞれコマがある状態で、かつ同じ文字の辺を通ってきた状態での最短距離としてbfsなどで求める
そうすると偶数長の場合は最終的に同じ頂点にいるので $ 2 \times dist[i][i] $ の最小、奇数長の場合、隣接した頂点にいるので 頂点 $ i $ と辺で繋がっている頂点を $ nv $ とすると $ 2 \times dp[i][nv] + 1 $ の最小が答えとなる
提出コード

    int N, M;
    cin >> N >> M;
    vector<vector<pair<int, int>>> edge(N);
    REP(i,M){
        int a, b;
        char c;
        cin >> a >> b >> c;
        --a, --b;
        edge[a].emplace_back(b, c-'a');
        edge[b].emplace_back(a, c-'a');
    }
    vector dist(N, vector(N, INF));
    dist[0][N-1] = 0;
    queue<pair<int, int>> q;
    q.emplace(0, N-1);
    while(!q.empty()){
        auto [a, b] = q.front(); q.pop();
        for(auto [na, c1] : edge[a]) for(auto [nb, c2] : edge[b]){
            if(c1 != c2) continue;
            if(dist[na][nb] < INF) continue;
            dist[na][nb] = dist[a][b] + 1;
            q.emplace(na, nb);
        }
    }
    int ans = INF;
    REP(i,N){
        chmin(ans, dist[i][i] * 2);
        for(auto [nv, c] : edge[i]){
            chmin(ans, dist[i][nv] * 2 + 1);
        }
    }
    if(ans == INF) ans = -1;
    cout << ans << endl;
}

AtCoder Beginner Contest 196(ABC196)

atcoder.jp

A - Difference Max

$ \mathcal{O}(1) $ でできそうだけど全探索できるのでする
提出コード

void solve(){
    int a, b, c, d;
    cin >> a >> b >> c >> d;
    int ans = -INF;
    for(int x = a;x<=b;x++){
        for(int y=c;y<=d;y++){
            chmax(ans, x - y);
        }
    }
    cout << ans << endl;
}

B - Round Down

.が出てくるまで出力すればいい
提出コード

void solve(){
    string S;
    cin >> S;
    REP(i,S.size()){
        if(S[i] == '.') break;
        cout << S[i];
    }
    cout << endl;
}

C - Doubled

同じ文字列を連結したものを考えると6桁の文字列のとき12桁の文字列になるので $ 10^6 $ 程度まで見れば十分
今見ている値を $ i, i $ の桁数を $ cnt $ とすると $ i \times 10^{cnt} + i \leq N $ を満たしていれば答えに加算していく
提出コード

void solve(){
    ll N;
    cin >> N;
    ll ans = 0;
    for(ll i=1;i<=1e6;i++){
        ll cnt = 1;
        ll tmp = i;
        while(tmp > 0){
            cnt *= 10;
            tmp /= 10;
        }
        ll num = i * cnt + i;
        if(num <= N) ans++;
    }
    cout << ans << endl;
}

D - Hanjo

$ HW \leq 16 $ なのでbit全探索などで全探索ができそうなので全探索を考える
マス $ (i, j) $ の番号を $ i \times W + j $ とすると、$ (i, j) $ に $ 2 \times 1 $ の畳を縦に置く、横に置く、置かないの3通り考えられる
そのため、愚直に上記を全探索しても $ \mathcal{O}(3^{HW} HW) $ で間に合う
すでに置いてあるところに置こうとする、最下段や右端のときに縦・横に置いたりなどは出来ないので注意
提出コード

void solve(){
    int H, W, A, B;
    cin >> H >> W >> A >> B;
    ll ans = 0;
    int n = H * W;
    auto dfs = [&](auto self, vector<int> &a) -> void{
        if(a.size() == n){
            int cnt = 0;
            REP(i,n) if(a[i] > 0) cnt++;
            if(cnt != A) return;
            vector<bool> used(n);
            REP(i,n){
                if(a[i] == 0) continue;
                else if(a[i] == 1){ // 縦
                    if(i >= (H - 1) * W) return;
                    if(used[i] or used[i+W]) return;
                    used[i] = true;
                    used[i+W] = true;
                }
                else{ // 横
                    if(i % W == W - 1) return;
                    if(used[i] or used[i+1]) return;
                    used[i] = true;
                    used[i+1] = true;
                }
            }
            ans++;
        }
        else{
            REP(i,3){
                a.push_back(i);
                self(self, a);
                a.pop_back();
            }
        }
    };
    vector<int> a;
    dfs(dfs, a);
    cout << ans << endl;
}

E - Filters

beatsで殴れる
関数を合成していくと、最終的には解説の図のようになる
https://img.atcoder.jp/ghi/f0bde453251ed936cbc861c1a9878e4c.png
最終的にある下限 $ mi $ 以下はすべて $ mi, $ ある上限 $ ma $ 以上はすべて $ ma, $ それ以外の場合 $ x ( mi \lt x \lt ma ) $ となる
そのため、$ f(-INF), f(INF) $ の値を実際に求めておき、$ t_i = 1 $ で加算される総和を $ sum $ として求めておくと各クエリの答えはclamp(x+sum, f(-INF), f(INF) として求められる
提出コード

void solve(){
    int N;
    cin >> N;
    ll mi = -LINF;
    ll ma = LINF;
    ll sum = 0;
    REP(i,N){
        ll a, t;
        cin >> a >> t;
        if(t == 1){
            sum += a;
            mi += a;
            ma += a;
        }
        else if(t == 2){
            chmax(ma, a);
            chmax(mi, a);
        }
        else{
            chmin(ma, a);
            chmin(mi, a);
        }
    }
    int Q;
    cin >> Q;
    while(Q--){
        ll x;
        cin >> x;
        cout << clamp(x + sum, mi, ma) << endl;
    }
}

パナソニックプログラミングコンテスト(AtCoder Beginner Contest 195)

atcoder.jp

A - Health M Death

$ H $ が $ M $ で割り切れればYes そうでないなら No
提出コード

void solve(){
    int M, H;
    cin >> M >> H;
    if(H % M == 0) cout << "Yes" << endl;
    else cout << "No" << endl;
}

B - Many Oranges

難しい
選んだ個数を $ i $ とすると達成できる重さの範囲は $ i \times A \leq x \leq i \times B $ を満たす $ x $ となる
よって $ i $ を全探索をし $ i \times A \leq 1000W \leq i \times B $ を満たす最小と最大の $ i $ を求めればいい
提出コード

void solve(){
    int A, B, W;
    cin >> A >> B >> W;
    W *= 1000;
    int mi = INF;
    int ma = 0;
    for(int i=1;i<=1e6;i++){
        if(i * A <= W and W <= i * B){
            chmax(ma, i);
            chmin(mi, i);
        }
    }
    if(mi == INF) cout << "UNSATISFIABLE" << endl;
    else cout << mi << " " << ma << endl;
}

C - Comma

$ 10 ^ 3 \leq x \lt 10 ^ 6 $ はコンマ1個、 $ 10 ^ 6 \leq x \lt 10 ^ 9 $ はコンマ2個・・・
のようになるのでif文などで数えればいい
提出コード

void solve(){
    ll N;
    cin >> N;
    ll ans = 0;
    vector<ll> v[4];
    if(N >= (ll)1e3){
        if(N < (ll)1e6) ans += N - 1e3 + 1;
        else ans += 1e6 - 1e3;
    }
    if(N >= (ll)1e6){
        if(N < (ll)1e9) ans += 2 * (ll)(N - 1e6 + 1);
        else ans += 2 * (ll)(1e9 - 1e6);
    }
    if(N >= (ll)1e9){
        if(N < (ll)1e12) ans += 3 * (ll)(N - 1e9 + 1);
        else ans += 3 * (ll)(1e12 - 1e9);
    }
    if(N >= (ll)1e12){
        if(N < (ll)1e15) ans += 4 * (ll)(N - 1e12 + 1);
        else ans += 4 * (ll)(1e15 - 1e12);
    }
    if(N == (ll)1e15) ans += 5;
    cout << ans << endl;
}

D - Shipping Center

価値が大きいものから順に箱に詰めていく
詰める箱は、今詰めようとしているものが入る最小の箱に入れていけばいい
提出コード

void solve(){
    int N, M, Q;
    cin >> N >> M >> Q;
    vector<int> W(N), V(N);
    REP(i,N) cin >> W[i] >> V[i];
    vector<int> X(M);
    REP(i,M) cin >> X[i];
    while(Q--){
        int L, R;
        cin >> L >> R;
        --L, --R;
        vector<int> idx(N);
        iota(idx.begin(), idx.end(), 0);
        sort(idx.begin(), idx.end(), [&](int a, int b){
            return V[a] > V[b];
        });
        vector<int> idx2(M);
        iota(idx2.begin(), idx2.end(), 0);
        sort(idx2.begin(), idx2.end(), [&](int a, int b){
            return X[a] < X[b];
        });
        vector<bool> used(M);
        ll ans = 0;
        for(auto i : idx){
            for(auto j : idx2){
                if(L <= j and j <= R) continue;
                if(!used[j] and X[j] >= W[i]){
                    ans += V[i];
                    used[j] = true;
                    break;
                }
            }
        }
        cout << ans << endl;
    }
}

E - Lucky 7 Battle

$ dp[i][j] := $ i回目の操作が終わったあとに $ T = j \pmod{7} $ になる状態があるかどうかをDPする
$ dp[N][0] $ となればTakahashiの勝ちとなるので後ろから考えると、$ S_N = T $ のとき $ 10r = 0 \pmod{7} $ もしくは、 $ 10r + S_{N} = 0 \pmod{7} $ のときTakahashiの勝ち
$ S_N = A $ のとき $ 10r \neq 0 \pmod{7} $ かつ $ 10r + S_{N} \neq 0 \pmod{7} $ のときAokiの勝ちとなるのでTakahashiが勝つには$ 10r = 0 \pmod{7} $ かつ $ 10r + S_{N} = 0 \pmod{7} $ であればいい
そのため、後ろからDPをしていき最終的に $ dp[0][0] $ が勝ちになるような状態が存在すればTakahashiの勝ち、そうでないならAokiの勝ちとなる
提出コード

void solve(){
    int N;
    string S, X;
    cin >> N >> S >> X;
    vector dp(N+1, vector<int>(7, 0));
    dp[N][0] |= 1;
    for(int i=N-1;i>=0;i--){
        int c = S[i] - '0';
        REP(k,7){
            if(X[i] == 'T') dp[i][k] |= (dp[i+1][(10*k)%7] or dp[i+1][(10*k+c)%7]);
            else dp[i][k] |= (dp[i+1][(10*k)%7] and dp[i+1][(10*k+c)%7]);
        }
    }
    cout << (dp[0][0] ? "Takahashi" : "Aoki") << endl;
}

F - Coprime Present

$ A \leq m \lt n \leq B $ を満たす $ (n, m) $ について
$ gcd(n, m) = gcd(n-m, m) \leq n - m \leq B - A $ が成り立つため、$ n, m $ が互いに素でない時、$ 2 \leq x \leq B - A $ を満たすある素数 $ x $ の倍数となる
したがって、$ 2 \leq x \leq B - A $ を満たす素数 $ x $ の倍数が高々1個しか含まれないような集合の数を求めればいい
$ dp[state] := $ 素数 $ x $ の倍数を選んだ状態が $ state $ のときの場合の数として、bitDPで求めればいい
提出コード

void solve(){
    ll A, B;
    cin >> A >> B;

    vector<bool> used(73);
    vector<int> prime;
    for(int i=2;i<=72;i++){
        if(used[i]) continue;
        prime.emplace_back(i);
        for(int j=2*i;j<=72;j+=i) used[j] = true;
    }
    int n = prime.size();
    vector dp(1<<n, 0LL);
    dp[0] = 1;
    ll ans = 0;
    for(ll j=A;j<=B;j++){
        int bit = 0;
        REP(k,n){
            if(j % prime[k] == 0){
                bit |= (1 << k);
            }
        }
        REP(i,1<<n){
            if((bit & i) > 0) continue;
            dp[i|bit] += dp[i];
        }
    }
    REP(i,1<<n) ans += dp[i];
    cout << ans << endl;
}

AtCoder Beginner Contest 194(ABC194)

atcoder.jp

A - I Scream

読解が難しい
問題文のとおり丁寧に場合分け
提出コード

void solve(){
    int A, B;
    cin >> A >> B;
    if(A + B >= 15 and B >= 8) cout << 1 << endl;
    else if(A + B >= 10 and B >= 3) cout << 2 << endl;
    else if(A + B >= 3) cout << 3 << endl;
    else cout << 4 << endl;
}

B - Job Assignment

全探索が間に合うので全探索をする
提出コード

void solve(){
    int N;
    cin >> N;
    vector<int> A(N), B(N);
    REP(i,N) cin >> A[i] >> B[i];
    int ans = INF;
    REP(i,N) REP(j,N){
        if(i == j) chmin(ans, A[i] + B[j]);
        else chmin(ans, max(A[i], B[j]));
    }
    cout << ans << endl;
}

C - Squared Error

$ \sum_{i=2}^{N} \sum_{j=1}^{i-1} (A_i - A_j)^2 = \sum_{i=2}^{N} \sum_{j=1}^{i-1} A_i ^2 + A_j ^2 - 2A_iA_j $ と式変形できるのでこれを求める
$ A_k^2 $ は $ A_i^2, A_j^2 $ と合わせて $ N - 1 $ 回出てくるので $ (N-1)A_k^2 $ 答えに加算する
$ \sum_{i=2}^{N} \sum_{j=1}^{i-1} - 2A_iA_j $ は $ -\sum_{i=2}^{N} 2A_i \sum_{j=1}^{i-1} A_j $ となり累積和を使えば求めることができる
提出コード

void solve(){
    int N;
    cin >> N;
    vector<ll> A(N);
    ll ans = 0;
    REP(i,N){
        cin >> A[i];
        ans += (N-1) * A[i] * A[i];
    }
    ll sum = A[0];
    for(int i=1;i<N;i++){
        ans -= 2LL * A[i] * sum;
        sum += A[i];
    }
    cout << ans << endl;
}

D - Journey

確率 $ \frac{1}{N} $ のものを引くまでの操作の期待値は $ N $ 回となる
今、$ i $ 個の頂点が連結だとすると連結成分が一つ増えるまでの操作の期待値は$ \frac{N}{i} $ 回となる
よって、$ \sum_{i=1}^{N-1} \frac{N}{i} $ が答え
提出コード

void solve(){
    int N;
    cin >> N;
    double ans = 0;
    for(int i=1;i<N;i++){
        ans += N / (double)i;
    }

    printf("%.12lf\n", ans);
}

E - Mex Min

想定解が賢すぎる、区間をsetで管理するやつを使えば結構楽
setだけだと重複要素に対応できないので今見ているM個の区間に出現する要素数を管理すればいい
提出コード

void solve(){
    int N, M;
    cin >> N >> M;
    vector<int> A(N);
    REP(i,N) cin >> A[i];
    vector<int> cnt(1515151);
    RangeSet<int> st;
    REP(i,M){
        cnt[A[i]]++;
        st.insert(A[i]);
    }
    int ans = st.mex();

    for(int i=M;i<N;i++){
        cnt[A[i-M]]--;
        if(cnt[A[i-M]] == 0) st.erase(A[i-M]);
        if(cnt[A[i]] == 0) st.insert(A[i]);
        cnt[A[i]]++;
        chmin(ans, st.mex());
    }
    cout << ans << endl;
}

キャディプログラミングコンテスト2021(AtCoder Beginner Contest 193)

atcoder.jp

A - Discount

$ 100 \times (1 - \frac{B}{A}) $ を出力する
提出コード

void solve(){
    double A, B;
    cin >> A >> B;
    printf("%.12lf\n", (1 - B / A) * 100);
}

B - Play Snuke

$ A_i \geq X_i $ のときは売り切れて買えない
それ以外の場合で、最小となる $ P_i $ を探す
提出コード

void solve(){
    int N;
    cin >> N;
    vector<ll> A(N), P(N), X(N);
    REP(i,N) cin >> A[i] >> P[i] >> X[i];
    ll ans = LINF;
    REP(i,N){
        if(A[i] >= X[i]) continue;
        chmin(ans, P[i]);
    }
    if(ans == LINF) ans = -1;
    cout << ans << endl;
}

C - Kaprekar Number

全探索をする
$ i \leq \sqrt{N} $ を満たす $ i $ について $ i^j \leq N ( j \geq 2 ) $ を満たすものをsetなどで管理すればいい
提出コード

void solve(){
    ll N;
    cin >> N;
    ll ans = N;
    set<ll> st;
    for(ll i=2;i*i<=N;i++){
        ll cur = i * i;
        while(cur <= N){
            st.insert(cur);
            cur *= i;
        }
    }
    cout << N - (ll)st.size() << endl;
}

D - Poker

二人の手札の場合の数は $ all = (9K - 8) (9K-9) $ となるので高橋くんが勝つときの場合の数を $ sum $ とすると $ \frac{sum}{all} $ が答え
$ sum $ については、高橋くんと青木くんの残りのカードについて全探索をして求めればいい
提出コード

void solve(){
    ll K;
    string S, T;
    cin >> K >> S >> T;
    vector<int> cntS(10), cntT(10);
    REP(i,4){
        cntS[S[i]-'0']++;
        cntT[T[i]-'0']++;
    }
    double sum = 0.0;
    double all = (9 * K - 8) * (9 * K - 9);
    for(int i=1;i<=9;i++){
        for(int j=1;j<=9;j++){
            if(i == j){
                if(cntS[i] + cntT[j] + 2 > K) continue;
            }
            else{
                if(cntS[i] + cntT[i] + 1 > K) continue;
                if(cntS[j] + cntT[j] + 1 > K) continue;
            }
            ll sumS = 0, sumT = 0;
            cntS[i]++;
            cntT[j]++;
            for(ll k=1;k<=9;k++){
                sumS += k * pow(10, cntS[k]);
                sumT += k * pow(10, cntT[k]);
            }
            cntS[i]--;
            cntT[j]--;
            if(sumS > sumT){
                if(i == j){
                    sum += (K - cntS[i] - cntT[i]) * (K - cntS[i] - cntT[i] - 1);
                }
                else sum += (K - cntS[i] - cntT[i]) * (K - cntS[j] - cntT[j]);
            }
        }
    }
    printf("%.12lf\n", sum / all);
}

E - Oversleeping

条件より $ (P + Q)a + P + q = (2X + 2Y)b + X + y (0 \leq q \lt Q, 0 \leq y \lt Y) $ を満たす $ a, b $ を見つけたい
左辺は $ P + Q $ の周期、右辺は $ 2X + 2Y $ の周期になっているので
\begin{align} t& \mod (P + Q) = P+q \\ t& \mod (2X + 2Y) = X+y \end{align}

を満たす最小の非負整数 $ t $ を求めることができればいい
これはACLのcrtを使えば求めることができる
提出コード

void solve(){
    ll X, Y, P, Q;
    cin >> X >> Y >> P >> Q;
    ll ans = 9 * LINF;
    for(int q = 0; q < Q; q++){
        for(int y = 0; y < Y; y++){
           auto [a, b] = crt({P+q, X+y}, {P+Q, 2*X+2*Y});
           if(a == 0 and b== 0) continue;
           chmin(ans, a);
        }
    }
    if(ans == 9 * LINF) cout << "infinity" << endl;
    else cout << ans << endl;
}