接着剤の精進日記

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

モノグサ プログラミングコンテスト2022(AtCoder Heuristic Contest 009)参加記

atcoder.jp

概要

BFSでの最短経路を初期解として焼きなましをする
焼きなましの遷移は

  • 1点追加(ランダムな1文字をランダムな位置に挿入)
  • 区間反転(2-optてきな)
  • 2点変更(ランダムな文字列2つをswap)
  • 1点削除(ランダムな1文字を削除)

遷移確率は30/30/30/10%(適当)
スコア計算にはビジュアライザで行っている計算を実装
7,290,672,610で最終15位
提出コード

ビジュアライザ

seed=2

知見

  • 行動をダブらせるのが強いらしい(同じ方向連打したほうが明後日の方向に行きにくくなる?)
  • ビームサーチが結構強い?(評価とか難しそう)

感想

中盤くらいに一瞬1位になったけどそこから大きく改善できずに沈んでしまった
1ページ目に残れたのは素直に嬉しいが、top10見えてただけにもう少し頑張りたかった
短期なのでガチャが当たった感が強いけど成功体験を得られてよかった
短期は2時間くらいでやることがなくなってしまうのでもう少し引き出しを増やしていきたいね

AtCoder Beginner Contest 244(ABC244)

atcoder.jp

A - Last Letter

S.back()を出力

提出コード

void solve(){
    int N;
    string S;
    cin >> N >> S;
    cout << S.back() << endl;
}

B - Go Straight and Turn Right

今見ている方向を管理する

提出コード

void solve(){
    int N;
    string T;
    cin >> N >> T;
    int x = 0;
    int y = 0;
    int dir = 0;
    const int dx[4] = {1, 0, -1, 0};
    const int dy[4] = {0, -1, 0, 1};
    for(char c : T){
        if(c == 'S'){
            x += dx[dir];
            y += dy[dir];
        }
        else{
            dir = (dir + 1) % 4;
        }
    }
    cout << x << " " << y << endl;
}

C - Yamanote Line Game

すでに使った整数を管理しておき、答えるときにはまだ使っていないものをどれか一つ出力する

提出コード

void solve(){
    int N;
    cin >> N;
    vector<int> used(2*N+1, 0);
    while(1){
        REP(i,2*N+1) if(!used[i]){
            cout << i + 1 << endl;
            used[i] = 1;
            break;
        }
        int x;
        cin >> x;
        if(x == 0){
            break;
        }
        else{
            used[x-1] = 1;
        }
    }
}

D - Swap Hats

STの転倒数を求める
転倒数の偶奇が異なる場合、操作は偶数回しか行えないのでNoとなる

提出コード

void solve(){
    vector<char> S(3), T(3);
    REP(i,3) cin >> S[i];
    REP(i,3) cin >> T[i];

    int inv = inversionNumber(S, T);
    if(inv == -1 or inv % 2 == 1) cout << "No" << endl;
    else cout << "Yes" << endl;
}

E - King Bombee

$ dp[i][j][k] := i $ 回目の移動のときに頂点 $ j $ にいて、$ X $ が $ k \pmod{2} $ 回出現しているときの場合の数としてDPを行う

提出コード

void solve(){
    int N, M, K, S, T, X;
    cin >> N >> M >> K >> S >> T >> X;
    --X, --S, --T;
    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 dp(K+1, vector(N, vector<mint>(2, 0)));
    dp[0][S][0] = 1;
    REP(i,K){
        REP(j,N){
            for(int nv : g[j]){
                REP(k,2){
                    if(nv == X){
                        dp[i+1][nv][k^1] += dp[i][j][k];
                    }
                    else dp[i+1][nv][k] += dp[i][j][k];
                }
            }
        }
    }
    cout << dp[K][T][0] << endl;
}

F - Shortest Good Path

$ dist[S][i] := $ 各頂点を通った回数が偶数か奇数かの状態 $ S $ のときに、最後に訪れた頂点が $ i $ のときの最短経路としてBFSを行う

提出コード

void solve(){
    int N, M;
    cin >> N >> M;
    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(1<<N, vector(N, LINF));
    queue<tuple<int, int, int>> q;
    REP(i,N){
        dist[1<<i][i] = 1;
        q.emplace(1<<i, i, 1);
    }
    while(!q.empty()){
        auto [bits, v, d] = q.front(); q.pop();
        for(int nv : g[v]){
            if(chmin(dist[bits^(1<<nv)][nv], dist[bits][v] + 1)) q.emplace(bits^(1<<nv), nv, dist[bits^(1<<nv)][nv]);
        }
    }
    ll ans = 0;
    REP(i,1,1<<N){
        ll mi = LINF;
        REP(j,N) chmin(mi, dist[i][j]);
        ans += mi;
    }
    cout << ans << endl;
}

AtCoder Beginner Contest 243(ABC243)

atcoder.jp

A - Shampoo

$ V = V \% (A + B + C) $ として考えていいので、この状態で誰が不足した状態になるかをシミュレートする

提出コード

void solve(){
    int V, A ,B, C;
    cin >> V >> A >> B >> C;
    V %= (A + B + C);
    if(V < A) cout << "F" << endl;
    else if(V < A + B) cout << "M" << endl;
    else cout << "T" << endl;
}

B - Hit and Blow

$ O(N^2) $ でも間に合うので全探索で数えればいい
もしくは、mapなどを使うことで計算量を改善できる

提出コード

void solve(){
    int N;
    cin >> N;
    vector<int> A(N), B(N);
    REP(i,N) cin >> A[i];
    REP(i,N) cin >> B[i];
    int ans1 = 0;
    REP(i,N) if(A[i] == B[i]) ans1++;
    map<int, int> mp;
    REP(i,N) mp[B[i]]++;
    int ans2 = 0;
    REP(i,N){
        int cnt = mp[A[i]];
        if(A[i] == B[i]) cnt--;
        ans2 += cnt;
    }
    cout << ans1 << endl;
    cout << ans2 << endl;
}

C - Collision 2

$ Y $ が同じ $ X $ について昇順に $ S_i $ を並べると、その文字列の中にRLが存在するかどうかを判定すればいい

提出コード

void solve(){
    int N;
    cin >> N;
    vector<int> X(N), Y(N);
    REP(i,N) cin >> X[i] >> Y[i];
    string S;
    cin >> S;
    map<int, vector<pair<int, char>>> mp;
    REP(i,N){
        mp[Y[i]].emplace_back(X[i], S[i]);
    }
    string ans = "No";
    for(auto [k, v] : mp){
        sort(ALL(v));
        REP(i,(int)v.size()-1){
            if(v[i].second == 'R' and v[i+1].second == 'L') ans = "Yes";
        }
    }
    cout << ans << endl;
}

D - Moves on Binary Tree

今いる頂点の番号を2進数で考え、文字列で表現する
Uの操作は、文字列の末尾をpopする
Lの操作は、末尾に0を追加する
Rの操作は、末尾に1を追加する
操作として置き換えられるのですべての操作を終えたあとに文字列を10進数に戻せばいい

提出コード

void solve(){
    ll N, X;
    cin >> N >> X;
    string ans = "";
    while(X > 0){
        ans += char(X % 2 + '0');
        X /= 2;
    }
    reverse(ALL(ans));
    string S;
    cin >> S;
    for(auto c : S){
        if(c == 'U') ans.pop_back();
        else if(c == 'L') ans += '0';
        else if(c == 'R') ans += '1';
    }
    ll sum = 0;
    reverse(ALL(ans));
    REP(i,ans.size()) if(ans[i] == '1') sum += (1LL << i);
    cout << sum << endl;
}

E - Edge Deletion

$ dist[i][j] := i $ から $ j $ までの最短距離として全点間の最短距離を予め求めておく
辺 $ i $ を削除してもいいかどうかは $ A_i $ から $ B_i $ までの距離が $ C_i $ 以下となるようなパスが他にあるかどうかで判定できる
よって、予めワーシャルフロイドで最短距離を求めておき、各辺に対し判定を行えばいい

提出コード

void solve(){
    int N, M;
    cin >> N >> M;
    vector dist(N, vector<ll>(N, LINF));
    vector<ll> A(M), B(M), C(M);
    REP(i,M){
        cin >> A[i] >> B[i] >> C[i];
        --A[i], --B[i];
        dist[A[i]][B[i]] = dist[B[i]][A[i]] = C[i];
    }
    REP(k,N) REP(i,N) REP(j,N) chmin(dist[i][j], dist[i][k] + dist[k][j]);
    int ans = 0;
    REP(i,M){
        bool ok = false;
        REP(j,N){
            if(dist[A[i]][j] + dist[j][B[i]] <= C[i]) ok = true;
        }
        ans += ok;
    }
    cout << ans << endl;
}

AtCoder Beginner Contest 242(ABC242)

atcoder.jp

A - T-shirt

$$ \left\{ \begin{array}{} 1 & (X \leq A) \\ \frac{C}{B - A} & (X \leq B) \\ 0 & else \end{array} \right. $$

提出コード

void solve(){
    int A, B, C, X;
    cin >> A >> B >> C >> X;
    double ans = 0;
    if(X <= A) ans = 1.0;
    else if(X <= B) ans = (double)C / (B - (A+1) + 1);
    else ans = 0;
    printf("%.12lf\n",ans);
}

B - Minimize Ordering

文字列をソートする

提出コード

void solve(){
    string S;
    cin >> S;
    sort(ALL(S));
    cout << S << endl;
}

C - 1111gal password

$ DP[i][j] = i $ 番目まで見た時に末尾の数字が $ j $ のときの場合の数としてDPをする

提出コード

void solve(){
    int N;
    cin >> N;
    vector<mint> dp(10);
    REP(i,1,10) dp[i] = 1;
    REP(_,N-1){
        vector<mint> nxt(10);
        REP(i,1,10){
            REP(j,1,10){
                if(abs(i - j) <= 1) nxt[j] += dp[i];
            }
        }
        swap(dp, nxt);
    }
    cout << accumulate(ALL(dp), mint(0)) << endl;
}

D - ABC Transform

$ S^{(t)} $ の $ k $ 文字目を返す関数を $ f(t, k) $ とする
$ t = 0 $ のとき $ S^{(0)}_k $
$ k = 0 $ のとき $ S^{(0)}_0 $ を $ t $ だけ進めた文字(A -> B -> C -> A-> ...)
それ以外のとき、$ f(t, k) $ は $ f(t-1, \frac{k}{2}) $ を $ k \% 2 + 1 $ だけ進めた文字として再帰関数で求める

提出コード

void solve(){
    string S;
    cin >> S;
    int Q;
    cin >> Q;

    auto g = [&](char c, ll k) -> char{
        return char((c - 'A' + k) % 3 + 'A');
    };

    auto f = [&](auto &&self, ll t, ll k) -> char{
        if(t == 0) return S[k];
        if(k == 0) return g(S[0], t);
        return g(self(self, t-1, k/2), k%2+1);
    };

    while(Q--){
        ll t, k;
        cin >> t >> k;
        cout << f(f, t, k-1) << endl;
    }
}

E - (∀x∀)

$ S $ を長さ $ \lceil \frac{N}{2} \rceil $ までの文字列として桁DPをする

提出コード

void solve(){
    int N;
    string S;
    cin >> N >> S;
    string T = S;
    reverse(ALL(T));
    REP(i,N/2) T[i] = S[i];
    vector dp(N+1, vector<mint>(2));
    dp[0][0] = 1;
    REP(i,ceil_div(N,2)) REP(j,2){
        for(char c='A';c<=(j ? 'Z' : S[i]);c++){
            dp[i+1][j or (c < S[i])] += dp[i][j];
        }
    }

    cout << dp[ceil_div(N,2)][1] + (T <= S) << endl;
}

G - Range Pairing Query

Mo’s algorithmが使える
区間を伸ばした時に、$ A_i $ の個数が奇数から偶数になるとき、その区間の答えが1加算され、逆に区間を縮小したときに $ A_i $ の個数が偶数から奇数になるときその区間の答えが1減る

提出コード

void solve(){
    int N;
    cin >> N;
    vector<int> A(N);
    REP(i,N) cin >> A[i];
    int Q;
    cin >> Q;
    vector<int> ans(Q);
    vector<int> cnt(N);
    int num = 0;
    auto add = [&](int idx){
        if(cnt[A[idx]]++ % 2 == 1) num++;
    };
    auto erase = [&](int idx){
        if(--cnt[A[idx]] % 2 == 1) num--;
    };
    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 << '\n';
}

AtCoder Beginner Contest 241(ABC241)

atcoder.jp

A - Digit Machine

$ a[a[a[0]]] $ が答え

提出コード

void solve(){
    vector<int> a(10);
    REP(i,10) cin >> a[i];
    cout << a[a[a[0]]] << endl;
}

B - Pasta

mapなどで長さごとに残りの個数を管理しておき、条件を満たすことができるかを判定する

提出コード

void solve(){
    int N, M;
    cin >> N >> M;
    vector<int> A(N), B(M);
    map<int, int> mp;
    REP(i,N){
        cin >> A[i];
        mp[A[i]]++;
    }
    REP(i,M) cin >> B[i];
    bool ok = true;
    REP(i,M){
        if(mp[B[i]] > 0){
            mp[B[i]]--;
        }
        else ok = false;
    }
    cout << (ok ? "Yes" : "No") << endl;
}

C - Connect 6

各マスから下、右、左下、右下の四方向に対しそれぞれ連続した6マスの中に黒マスが4つ以上あれば条件を満たすことができる

提出コード

void solve(){
    int N;
    cin >> N;
    vector<string> S(N);
    REP(i,N) cin >> S[i];
    bool ok = false;
    REP(i,N) REP(j,N){
        int dx[4] = {1, 0, 1, -1};
        int dy[4] = {0, 1, 1, 1};
        REP(d,4){
            int black = 0, white = 0;
            REP(k,6){
                int nh = i + dx[d] * k;
                int nw = j + dy[d] * k;
                if(nh < 0 or nh >= N or nw < 0 or nw >= N) break;
                if(S[nh][nw] == '#') black++;
                else white++;
            }
            if((black == 6) or (black == 5 and white == 1) or (black == 4 and white == 2)){
                ok = true;
            }
        }
    }
    cout << (ok ? "Yes" : "No") << endl;
}

D - Sequence Query

multisetで値を管理しながら愚直に求めればいい

提出コード

void solve(){
    int Q;
    cin >> Q;
    multiset<ll> mst;
    mst.insert(-1);
    mst.insert(2*LINF);
    while(Q--){
        int q;
        cin >> q;
        if(q == 1){
            ll x;
            cin >> x;
            mst.insert(x);
        }
        else if(q == 2){
            ll x, k;
            cin >> x >> k;
            auto itr = mst.upper_bound(x);
            --k;
            if(*itr == 2*LINF or *itr > x) itr--;
            bool ok = true;
            while(k--){
                if(*itr == -1){
                    ok = false;
                    break;
                }
                itr--;
            }
            cout << (ok ? *itr : -1) << endl;
        }
        else if(q == 3){
            ll x, k;
            cin >> x >> k;
            --k;
            auto itr = mst.lower_bound(x);
            bool ok = true;
            if(*itr == 2*LINF){
                ok = false;
                k = 0;
            }
            while(k--){
                itr++;
                if(*itr == 2*LINF){
                    ok = false;
                    break;
                }
            }
            cout << (ok ? *itr : -1) << endl;
        }
    }
}

E - Putting Candies

周期性があるのでダブリングなどで求めることができる

提出コード

void solve(){
    ll N, K;
    cin >> N >> K;
    vector<ll> A(N);
    REP(i,N) cin >> A[i];
    vector val(40, vector<ll>(N, 0));
    vector to(40, vector<ll>(N, 0));
    REP(i,N){
        val[0][i] = A[i];
        to[0][i] = (i + A[i]) % N;
    }
    REP(i,40-1) REP(j,N){
        to[i+1][j] = to[i][to[i][j]];
        val[i+1][j] = val[i][j] + val[i][to[i][j]];
    }
    ll ans = 0;
    ll cur = 0, tmp = 0;
    REP(i,40){
        if(K >> i & 1){
            ans += val[i][cur];
            cur = to[i][cur];
        }
    }
    cout << ans << endl;
}

F - Skate

辿り着けるマスは障害物のあるマスの上下左右の4つのみなのでBFSなどで求めることができる
辿り着けるマスの候補をx, y座標それぞれについて座圧し、その個数分setなどで障害物のあるマスを管理する
今いるマスから次のマスへは二分探索で求めることができる

提出コード

void solve(){
    int H, W, N;
    cin >> H >> W >> N;
    int sh, sw, gh, gw;
    cin >> sh >> sw >> gh >> gw;
    Compress<int> cmp;
    cmp.add(0);
    cmp.add(INF);
    vector<int> X(N), Y(N);
    REP(i,N){
        cin >> X[i] >> Y[i];
        cmp.add(X[i]);
        cmp.add(X[i]-1);
        cmp.add(X[i]+1);
        cmp.add(Y[i]);
        cmp.add(Y[i]-1);
        cmp.add(Y[i]+1);
    }
    cmp.build();
    int sz = cmp.size();
    vector<set<int>> st_x(sz+10), st_y(sz+10);
    st_x[cmp.get(sh)].insert(0);
    st_x[cmp.get(gh)].insert(W+1);
    st_y[cmp.get(sw)].insert(0);
    st_y[cmp.get(gw)].insert(H+1);
    REP(i,sz){
        st_x[i].insert(0);
        st_x[i].insert(W+1);
        st_y[i].insert(0);
        st_y[i].insert(H+1);
    }
    REP(i,N){
        st_x[cmp.get(X[i])].insert(Y[i]);
        st_y[cmp.get(Y[i])].insert(X[i]);
    }

    queue<pair<int, int>> q;
    map<pair<int, int>, int> mp;
    q.emplace(sh, sw);
    mp[{sh, sw}] = 0;
    while(!q.empty()){
        auto [h, w] = q.front(); q.pop();
        {
            // 上
            auto itr = st_y[cmp.get(w)].lower_bound(h);
            --itr;
            int nh = *itr;
            if(nh == 0){}
            else{
                nh++;
                if(!mp.count({nh, w})){
                    q.emplace(nh, w);
                    mp[{nh, w}] = mp[{h, w}] + 1;
                }
            }
        }
        {
            // 下
            auto itr = st_y[cmp.get(w)].lower_bound(h);
            if(*itr != h) itr--;
            itr++;
            int nh = *itr;
            if(nh == H + 1){}
            else{
                nh--;
                if(!mp.count({nh, w})){
                    q.emplace(nh, w);
                    mp[{nh, w}] = mp[{h, w}] + 1;
                }
            }
        }
        {
            // 左
            auto itr = st_x[cmp.get(h)].lower_bound(w);
            itr--;
            int nw = *itr;
            if(nw == 0){}
            else{
                nw++;
                if(!mp.count({h, nw})){
                    q.emplace(h, nw);
                    mp[{h, nw}] = mp[{h, w}] + 1;
                }
            }
        }
        {
            // 右
            auto itr = st_x[cmp.get(h)].lower_bound(w);
            if(*itr != w) itr--;
            itr++;
            int nw = *itr;
            if(nw == W + 1){}
            else{
                nw--;
                if(!mp.count({h, nw})){
                    q.emplace(h, nw);
                    mp[{h, nw}] = mp[{h, w}] + 1;
                }
            }
        }
    }
    cout << (mp[{gh, gw}] ? mp[{gh, gw}] : -1) << endl;
}