接着剤の精進日記

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

AtCoder Beginner Contest 189(ABC189)

atcoder.jp

A - Slot

$ C_1 \neq C_2 $ かつ $ C_2 \neq C_3 $ のときWon
それ以外の場合Lost
提出コード

void solve(){
    char c1, c2, c3;
    cin >> c1 >> c2 >> c3;
    if(c1 == c2 and c2 == c3) cout << "Won" << endl;
    else cout << "Lost" << endl;
}

B - Alcoholic

誤差があるので整数同士での比較にする
$ i $番目のアルコールを飲んだ時に$ V_1 P_1 + V_2 P_2 + ... + V_i P_i \gt 100X $
となる$ i $を出力すればいい
提出コード

void solve(){
    int N;
    ll X;
    cin >> N >> X;
    ll cur = 0;
    X *= 100;
    REP(i,N){
        ll V, P;
        cin >> V >> P;
        cur += V * P;
        dump(cur);
        if(cur > X){
            cout << i + 1 << endl;
            return;
        }
    }
    cout << -1 << endl;
}

C - Mandarin Orange

$ \mathcal{O}(N^2) $が通るので全ての区間について計算すればいい
提出コード

void solve(){
    int N;
    cin >> N;
    vector<ll> A(N);
    REP(i,N) cin >> A[i];
    ll ans = 0;
    REP(i,N){
        ll mi = LINF;
        for(int j=i;j<N;j++){
            chmin(mi, A[j]);
            chmax(ans, mi * (j-i+1));
        }
    }
    cout << ans << endl;
}

D - Logical Expression

$ dp[i][j] := i $番目を見ているときにその状態が$ j $のときの場合の数としてdpをする
$ j = 0 $のときFalse $ j = 1 $のときTrueとすると$ dp[N][1] $が答え
提出コード

void solve(){
    int N;
    cin >> N;
    vector<string> S(N);
    REP(i,N){
        cin >> S[i];
    }
    vector dp(N+1, vector(2, 0LL));
    dp[0][0] = dp[0][1] = 1;
    REP(i,N){
        if(S[i] == "OR"){
            // trueを選ぶ
            dp[i+1][1] += dp[i][0];
            dp[i+1][1] += dp[i][1];
            // falseを選ぶ
            dp[i+1][1] += dp[i][1];
            dp[i+1][0] += dp[i][0];
        }
        else{
            // true
            dp[i+1][1] += dp[i][1];
            dp[i+1][0] += dp[i][0];
            // false
            dp[i+1][0] += dp[i][0];
            dp[i+1][0] += dp[i][1];
        }
    }
    cout << dp[N][1] << endl;
}

E - Rotate and Flip

各操作を変換行列で表すと予め累積積を求めておくことで$ M $回目の操作ついて$ \mathcal{O}(1) $で求められる
操作$ i $の変換行列を$ A_i $とすると各変換行列は以下のようになる
$$ A_1 = \left( \begin{array}{ccc} 0 & 1 & 0 \\ -1 & 0 & 0 \\ 0 & 0 & 1 \end{array} \right) $$ $$ A_2 = \left( \begin{array}{ccc} 0 & -1 & 0 \\ 1 & 0 & 0 \\ 0 & 0 & 1 \end{array} \right) $$ $$ A_3 = \left( \begin{array}{ccc} -1 & 0 & 2p \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{array} \right) $$ $$ A_4 = \left( \begin{array}{ccc} 1 & 0 & 0 \\ 0 & -1 & 2p \\ 0 & 0 & 1 \end{array} \right) $$ $ i $回目までの変換行列の積を$ B_i $とすると$ j $の$ i $回目の操作後の座標は $$ B_i \left( \begin{array}{c} X _j \\ Y _j \\ 1 \end{array} \right) $$ で求められる
提出コード

mat mul(mat& A,mat& B){
    mat C(A.size(), vec(B[0].size()));
    for(int i=0;i<A.size();i++){
        for(int j=0;j<B[0].size();j++){
            for(int k=0;k<A[0].size();k++){
                C[i][j] += A[i][k] * B[k][j];
            }
        }
    }
    return C;
}

void solve(){
    int N;
    cin >> N;
    vector<mat> a(N, vector(3, vector(3, 0LL)));
    REP(i,N){
        int X, Y;
        cin >> X >> Y;
        a[i] = mat(3, vector(3, 0LL));
        a[i][0][0] = X;
        a[i][1][0] = Y;
        a[i][2][0] = 1;
    }
    int M;
    cin >> M;
    vector<mat> op(M+1, vector(3, vector(3, 0LL)));
    REP(i,M){
        int q;
        cin >> q;
        if(q == 1){
            op[i][0][1] = 1;
            op[i][1][0] = -1;
            op[i][2][2] = 1;
        }
        if(q == 2){
            op[i][0][1] = -1;
            op[i][1][0] = 1;
            op[i][2][2] = 1;
        }
        if(q == 3){
            ll p;
            cin >> p;
            op[i][0][0] = -1;
            op[i][0][2] = 2*p;
            op[i][1][1] = 1;
            op[i][2][2] = 1;
        }
        if(q == 4){
            ll p;
            cin >> p;
            op[i][0][0] = 1;
            op[i][1][1] = -1;
            op[i][1][2] = 2*p;
            op[i][2][2] = 1;
        }
    }
    REP(i,M-1) op[i+1] = mul(op[i+1], op[i]);
    int Q;
    cin >> Q;
    REP(i,Q){
        ll A, B;
        cin >> A >> B;
        --A, --B;
        if(A == -1){
            cout << a[B][0][0] << " " << a[B][1][0] << endl;
            continue;
        }
        auto tmp = mul(op[A], a[B]);
        cout << tmp[0][0] << " " << tmp[1][0] << endl;
    }
}

F - Sugoroku2

解説AC
$ dp[i] := $ マス$ i $からゴールまでに必要な回数の期待値とすると
$$ dp[i] = \begin{cases} \frac{dp[i+1] + dp[i+2] + \cdots + dp[i+M]}{M} + 1 & (マスiが振り出しじゃない) \\ dp[0] & (マスiが振り出し) \end{cases} $$ 求めたい答えを変数に置き換えると$( dp[0] = x )$
$ x = ax + b $の1次式で表すことが出来る
これを解くことで$ x = \frac{b}{1-a} $となり、$ dp[0] $を求めることが出来る
ただし、$ a = 1 $の時答えは-1となる
提出コード

void solve(){
    int N, M, K;
    cin >> N >> M >> K;
    vector<int> A(N+10);
    REP(i,K){
        int a;
        cin >> a;
        A[a] = 1;
    }
    vector<pair<double, double>> dp(N+10);
    pair<double, double> sum = {0.0, 0.0};
    for(int i=N-1;i>=0;i--){
        if(A[i]) dp[i] = {1.0, 0};
        else{
            dp[i].first += sum.first / M;
            dp[i].second += sum.second / M + 1.0;
        }
        sum.first += dp[i].first;
        sum.second += dp[i].second;
        if(i + M <= N){
            sum.first -= dp[i+M].first;
            sum.second -= dp[i+M].second;
        }
    }
    if(dp[0].first + EPS > 1.0) cout << -1 << endl;
    else cout << setprecision(15) << dp[0].second / (1 - dp[0].first) << endl;
}

AtCoder Beginner Contest 188(ABC188)

atcoder.jp

A - Three-Point Shot

$ \min(a, b) + 3 \gt \max(a, b) $かどうかをチェック
提出コード

void solve(){
    int X, Y;
    cin >> X >> Y;
    if(X > Y) swap(X, Y);
    if(X + 3 > Y) cout << "Yes" << endl;
    else cout << "No" << endl;
}

B - Orthogonality

問題文の通りに$ A_i \times B_i $の総和を求めてそれが0かどうか調べる
提出コード

void solve(){
    int N;
    cin >> N;
    ll sum = 0;
    vector<int> A(N), B(N);
    REP(i,N) cin >> A[i];
    REP(i,N) cin >> B[i];

    REP(i,N) sum += A[i] * B[i];
    if(sum == 0) cout << "Yes" << endl;
    else cout << "No" << endl;
}

C - ABC Tournament

$ N $が小さいので愚直にシミュレートをすればいい
シミュレートして最後に負けた人が答え
提出コード

void solve(){
    int N;
    cin >> N;
    vector<ll> A(1<<N);
    REP(i,1<<N) cin >> A[i];
    vector<int> idx(1<<N);
    iota(idx.begin(), idx.end(), 0);
    int ans = 0;
    while((int)idx.size() > 1){
        vector<int> next;
        for(int i=0;i<(int)idx.size();i+=2){
            if(A[idx[i]] > A[idx[i+1]]){
                ans = idx[i+1];
                next.emplace_back(idx[i]);
            }
            else{
                ans = idx[i];
                next.emplace_back(idx[i+1]);
            }
        }
        idx = next;
    }
    cout << ans + 1 << endl;
}

D - Snuke Prime

imos法で一日あたりのコストを$ imos_i $として求めておけば各日において
$ \min(imos_i, C) $の総和が答えとなる
ただし、値が大きいので座圧する必要がある
イベントソートだと座圧しなくても解くことが出来る
提出コード

void solve(){
    int N;
    ll C;
    cin >> N >> C;
    vector<ll> a(N), b(N), c(N);
    Compress<ll> cmp;
    REP(i,N){
        cin >> a[i] >> b[i] >> c[i];
        cmp.add(a[i]);
        cmp.add(b[i]+1);
    }
    cmp.build();
    int sz = cmp.size();
    vector<ll> imos(sz+1);
    REP(i,N){
        imos[cmp.get(a[i])] += c[i];
        imos[cmp.get(b[i]+1)] -= c[i];
    }
    REP(i,sz) imos[i+1] += imos[i];
    ll ans = 0;
    REP(i,sz-1){
        ans += min(imos[i], C) * (cmp[i+1] - cmp[i]);
    }
    cout << ans << endl;
}

提出コード(イベントソート)

void solve(){
    int N;
    ll C;
    cin >> N >> C;
    vector<ll> a(N), b(N), c(N);
    vector<pair<ll, ll>> event;
    REP(i,N){
        cin >> a[i] >> b[i] >> c[i];
        event.emplace_back(a[i], c[i]);
        event.emplace_back(b[i]+1, -c[i]);
    }
    sort(event.begin(), event.end());
    ll ans = 0, pre = 0, cur_c = 0;
    for(auto [d, cost] : event){
        ans += min(cur_c, C) * (d - pre);
        pre = d;
        cur_c += cost;
    }
    cout << ans << endl;
}

E - Peddler

制約から与えられるグラフはDAGとなる
そのため町$ i $から移動できる町を$ j $とすると
$ dp[j] = \min(dp[j], dp[i], A_i) $として町$ j $に辿り着ける町の中で金の最安値として$ i $の昇順にdpをする
そうすると各町$ i $の$ A_i - dp[i] $の最大値が答え
解説の解法2だとDAGじゃなくても解ける
提出コード

void solve(){
    int N, M;
    cin >> N >> M;
    vector<ll> A(N);
    REP(i,N) cin >> A[i];
    vector<vector<int>> g(N);
    REP(i,M){
        int x, y;
        cin >> x >> y;
        g[--x].emplace_back(--y);
    }
 
    vector<ll> dp(N, LINF);
    REP(i,N){
        for(auto nv : g[i]) chmin(dp[nv], min(A[i], dp[i]));
    }
    ll ans = -LINF;
    REP(i,N) chmax(ans, A[i] - dp[i]);
    cout << ans << endl;
}

提出コード(解法2)

void solve(){
    int N, M;
    cin >> N >> M;
    vector<ll> A(N);
    REP(i,N) cin >> A[i];
    vector<vector<int>> g(N);
    REP(i,M){
        int x, y;
        cin >> x >> y;
        g[--x].emplace_back(--y);
    }
    vector<int> idx(N);
    iota(idx.begin(), idx.end(), 0);
    sort(idx.begin(), idx.end(), [&](int x, int y){
        return A[x] < A[y];
    });
    vector<bool> used(N);
    ll ans = -LINF;
    for(int i : idx){
        if(used[i]) continue;
        queue<int> q;
        q.emplace(i);
        while(!q.empty()){
            int v = q.front(); q.pop();
            for(auto nv : g[v]){
                if(used[nv]) continue;
                chmax(ans, A[nv] - A[i]);
                q.emplace(nv);
                used[nv] = true;
            }
        }
    }
    cout << ans << endl;
}

F - +1-1x2

類題がAGCにあるのでよしなにする
解説(AGCのだけど)はアルメリアさんのものがわかりやすいと思うので割愛(サボり)
atcoder.jp
betrue12.hateblo.jp

提出コード

void solve(){
    ll X, Y;
    cin >> X >> Y;
    ll ans = LINF;
    map<ll, ll> memo;
    priority_queue<LP, vector<LP>, greater<LP>> pq;
    pq.emplace(0, Y);
    while(!pq.empty()){
        auto [v, x] = pq.top(); pq.pop();

        if(v >= ans) continue;
        chmin(ans, v + abs(X-x));

        int r = (x & 1);
        ll nv = v + r + 1;
        int d = 2;
        if(nv < ans && (!memo.count(x / d) || nv < memo[x/d])){
            pq.emplace(nv, x / d);
            memo[x / d] = nv;
        }
        nv = v + (d - r) + 1;
        if(nv < ans && (!memo.count(x / d + 1) || nv < memo[x/d + 1])){
            pq.emplace(nv, x / d + 1);
            memo[x / d + 1] = nv;
        }
    }
    cout << ans << endl;
}

AtCoder Beginner Contest 187(ABC187)

atcoder.jp

A - Large Digits

桁和を求めてそのmaxを出力する
提出コード

void solve(){
    int A, B;
    cin >> A >> B;
    int suma = 0, sumb = 0;
    while(A > 0){
        suma += A % 10;
        A /= 10;
    }
    while(B > 0){
        sumb += B % 10;
        B /= 10;
    }
    cout << max(suma, sumb) << endl;
}

B - Gentle Pairs

傾きは$ \frac{y_i - y_j}{x_j - x_i} $なので$ -1 \leq \frac{y_j - y_i}{x_j - x_i} \leq 1 $を満たすものを数えればいい
提出コード

void solve(){
    int N;
    cin >> N;
    vector<int> x(N), y(N);
    REP(i,N) cin >> x[i] >> y[i];
    int ans = 0;
    REP(i,N) for(int j=i+1;j<N;j++){
        int xx = x[j] - x[i];
        int yy = y[j] - y[i];
        if(abs(yy) <= abs(xx)) ans++;
    }
    cout << ans << endl;
}

C - 1-SAT

!がついているものとついていないものをそれぞれsetなどで管理する
!がついている文字列を見ていき、!を除いた文字列がもう片方のsetに入っていればそれを出力する
提出コード

void solve(){
    int N;
    cin >> N;
    vector<string> S(N);
    REP(i,N) cin >> S[i];
    set<string> st;
    set<string> ex;
    REP(i,N){
        if(S[i][0] == '!') ex.insert(S[i]);
        else st.insert(S[i]);
    }
    for(auto s : ex){
        if(st.find(s.substr(1,(int)s.size())) != st.end()){
            cout << s.substr(1, (int)s.size()) << endl;
            return;
        }
    }
    cout << "satisfiable" << endl;
}

D - Sum of difference

$ i $番目の町で演説すると高橋くんは$ A_i + B_i $票、青木くんは$ - A_i $票得られる
よって高橋くんが得する票数はその差分$ A_i + B_i - (- A_i) = 2A_i + B_i $になる
そのためこの降順にソートして貪欲に演説していけばいい
提出コード

void solve(){
    int N;
    cin >> N;
    // vector<ll> A(N), B(N);
    vector<pair<ll, ll>> v(N);
    ll sumA = 0;
    REP(i,N){
        ll A, B;
        cin >> A >> B;
        sumA += A;
        v[i].first = 2 * A + B;
        v[i].second = A;
    }
    sort(v.rbegin(), v.rend());
    ll sum = 0;
    REP(i,N){
        sum += v[i].first - v[i].second;
        sumA -= v[i].second;
        // dumps(v[i].first, v[i].second);
        if(sum > sumA){
            cout << i + 1 << endl;
            return;
        }
    }
}

E - Through Path

適当な頂点を根とした根付き木として考える
そうすると各クエリはある頂点の部分木に$ x $加算するか部分木以外の全頂点に$ x $加算するクエリになる
どちらの点が部分木に含まれるかどうかは予めdfsなどで各頂点の深さを求めておき、深い方の頂点を部分木の頂点とすればいい
ここで部分木以外の全頂点に加算するクエリを全頂点に$ x $加算し、部分木に$ - x$加算するクエリとすれば全て部分木に加算するクエリにまとめられる
全体に加算する値と各頂点にその頂点の部分木にどれだけ加算するかの値を持っておけば最後に根から木上の累積和を取ればいい
オイラーツアー + imos法とかでも解ける
提出コード

void solve(){
    int n;
    cin >> n;
    vector<vector<int>> g(n);
    vector<int> a(n), b(n);
    REP(i,n-1){
        cin >> a[i] >> b[i];
        g[--a[i]].emplace_back(--b[i]);
        g[b[i]].emplace_back(a[i]);
    }
    vector<ll> plus(n), minus(n);
    int q;
    cin >> q;
    ll sum = 0;
    vector<int> depth(n);
    auto rec = [&](auto && self, int v, int par, int dep = 0)->void{
        depth[v] = dep;
        for(auto nv : g[v]){
            if(nv == par) continue;
            self(self, nv, v, dep + 1);
        }
    };
    rec(rec, 0, -1);
    while(q--){
        int t, e, x;
        cin >> t >> e >> x;
        --e;
        dumps(depth[a[e]], depth[b[e]]);
        if(t == 1){
            if(depth[a[e]] < depth[b[e]]){
                sum += x;
                minus[b[e]] += x;
            }
            else{
                plus[a[e]] += x;
            }
        }
        else{
            if(depth[a[e]] < depth[b[e]]){
                plus[b[e]] += x;
            }
            else{
                sum += x;
                minus[a[e]] += x;
            }
        }
    }
    vector<ll> ans(n);
    auto dfs = [&](auto && self, int v, int par, ll num) -> void{
        ans[v] = sum + num + plus[v] - minus[v];
        for(auto nv : g[v]){
            if(nv == par) continue;
            self(self, nv, v, num + plus[v] - minus[v]);
        }
    };
    dfs(dfs, 0, -1, 0);
    REP(i,n) cout << ans[i] << endl;
}

提出コード(オイラーツアー + imos)

void solve(){
    int N;
    cin >> N;
    vector<vector<int>> g(N);
    vector<pair<int, int>> edge(N);
    REP(i,N-1){
        int a, b;
        cin >> a >> b;
        g[--a].emplace_back(--b);
        g[b].emplace_back(a);
        edge[i] = make_pair(a, b);
    }
    vector<int> ls(N), rs(N);
    int idx = 0;
    auto dfs = [&](auto && self, int v, int par) -> void{
        ls[v] = idx++;
        for(auto nv : g[v]){
            if(nv == par) continue;
            self(self, nv, v);
        }
        rs[v] = idx;
    };
    dfs(dfs, 0, -1);
    vector<ll> imos(N+1);
    int q;
    cin >> q;
    while(q--){
        int t, e, x;
        cin >> t >> e >> x;
        auto [a, b] = edge[--e];
        if(t == 2) swap(a, b);
        if(ls[a] < ls[b]){
            imos[0] += x;
            imos[N] -= x;
            imos[ls[b]] -= x;
            imos[rs[b]] += x;
        }
        else{
            imos[ls[a]] += x;
            imos[rs[a]] -= x;
        }
    }
    REP(i,N) imos[i+1] += imos[i];
    REP(i,N) cout << imos[ls[i]] << "\n";
}

F - Close Group

まず、全ての集合についてその集合がクリークかどうかを前計算で求める
ある集合にその集合に含まれない頂点を1つ加えるとすると、その頂点から集合に含まれる全ての頂点にへの辺が存在すればその頂点を加えた集合もクリークになる
その次に、ある集合が最小何個のクリークで構成出来るかbitDPで求めればいい
これはある集合に対し、その部分集合を列挙すれば良く、$ \mathcal{O}(3^N) $で出来る
提出コード

void solve(){
    int N, M;
    cin >> N >> M;
    vector<int> g(N);
    REP(i,M){
        int A, B;
        cin >> A >> B;
        --A, --B;
        g[A] |= (1 << B);
        g[B] |= (1 << A);
    }
    vector<int> dp(1<<N, INF);
    dp[0] = 1;
    REP(i,1<<N) REP(j,N) if(i >> j & 1){
        int tar = i - (1 << j);
        if(dp[tar] <= 1 and (g[j] & tar) == tar) dp[i] = 1;
    }
    for(int i=1;i<(1<<N);i++){
        for(int j=i;j>0;j=(j-1)&i){
            chmin(dp[i], dp[i^j] + dp[j]);
        }
    }
    cout << dp[(1<<N)-1] << endl;
}

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

atcoder.jp

A - Brick

$ \lceil \frac{N}{W} \rceil $ を出力
提出コード

void solve(){
    int N, W;
    cin >> N >> W;
    cout << N / W << endl;
}

B - Blocks on Grid

一番小さい値に合わせればいいのでその差分の和が答え
提出コード

void solve(){
    int H, W;
    cin >> H >> W;
    vector A(H, vector(W, 0));
    int mi = INF;
    REP(i,H) REP(j,W){
        cin >> A[i][j];
        chmin(mi, A[i][j]);
    }
    int ans = 0;
    REP(i,H) REP(j,W) ans += A[i][j] - mi;
    cout << ans << endl;
}

C - Unlucky 7

正の整数を10進法と8進法に変換するのは愚直にやっても間に合うので全探索すればいい
提出コード

void solve(){
    int N;
    cin >> N;
    int ans = 0;
    for(int i=1;i<=N;i++){
        int tmp = i;
        bool ok = false;
        while(tmp > 0){
            int num = tmp % 10;
            if(num == 7) ok = true;
            tmp /= 10;
        }
        tmp = i;
        while(tmp > 0){
            int num = tmp % 8;
            if(num == 7) ok = true;
            tmp /= 8;
        }
        if(ok) ans++;
    }
    cout << N - ans << endl;
}

D - Sum of difference

絶対値が扱いにくいので降順ソートをすると$ A_i - A_j \geq 0 $となって扱いやすい
$ A_i $を固定すると
$ A_i - A_{i + 1} + A_i - A_{i+2} + ... + A_i - A_{N - 1} = A_i * (N - i - 1) - \sum _{j = i + 1} ^{N - 1} A_j $
となるので、予め累積和を求めておけばいい
提出コード

void solve(){
    int N;
    cin >> N;
    vector<ll> A(N);
    REP(i,N) cin >> A[i];
    sort(A.rbegin(), A.rend());
    vector<ll> cum(N+1);
    for(int i=N-1;i>=0;i--) cum[i] = cum[i+1] + A[i];
    ll ans = 0;
    REP(i,N){
        ans += A[i] * (N - i -1) - cum[i+1];
    }
    cout << ans << endl;
}

E - Throne

何回移動したかを$ x $とすると$ S + Kx \equiv 0 \pmod N $を満たす$ x $を求められればいい
これは$ K $の逆元が存在すれば求めることが出来る
まず、$ g = gcd(N, S, K) $とし、$N = N / g, S = S / g, K = K / g $と置き換える
$gcd(N, K) \neq 1 $のとき、$ K $の逆元が存在しないので-1
それ以外の場合逆元を求めれば良く、ACLinv_modを使えばこれを求められる
また、$ y = Kx $とし、$ S + Kx \equiv 0 \pmod N $を

  • $ y \equiv 0 \pmod K $
  • $ y \equiv -S \pmod N $

とすれば、中国剰余定理で解が存在するかどうかと、存在すればその解を求めることが出来る
提出コード

void solve(){
    ll N, S, K;
    cin >> N >> S >> K;
    ll g = gcd(gcd(N, S), K);
    N /= g, S /= g, K /= g;
    if(gcd(N, K) != 1){
        cout << -1 << endl;
        return;
    }
    cout << (N - S + N) % N * inv_mod(K, N) % N << endl;
}

提出コード(中国剰余定理)

void solve(){
    ll N, S, K;
    cin >> N >> S >> K;
    auto [y, z] = crt({0, N-S}, {K, N});
    if(y == 0) cout << -1 << endl;
    else cout << y / K << endl;
}

F - Rook on Grid

移動方法を考えると、最初に右に移動し、次に下に移動する、もしくは最初に下に移動し、次に右に移動するの2パターン考えられる
右・下の移動を考えると、各列において最初にぶつかる障害物までの位置を求めることで移動可能なマスを数えられ、下・右の場合も同様に求められる
$ w_i $を$ i $列目に一番上にある障害物の位置、$ h_i $を$ i $行目の一番左にある障害物の位置とすると
それぞれの移動可能なマスは$ \sum _{i = 0} ^{h_0} w_i + \sum _{i = 0} ^{w_0} h_i $となる
ただし、重複して数えているマスがあるので重複マスを引いて上げる必要がある
上から順に各行を見ていき、各マスがすでに障害物が出現したかどうかをBITなどで管理し、その総和を引いていけばいい
提出コード

void solve(){
    int H, W, M;
    cin >> H >> W >> M;
    vector<int> h(H, W), w(W, H);
    REP(i,M){
        int x, y;
        cin >> x >> y;
        --x, --y;
        chmin(w[y], x);
        chmin(h[x], y);
    }
    ll ans = 0;
    REP(i,w[0]) ans += h[i];
    REP(i,h[0]) ans += w[i];
    BIT<int> bit(W);
    REP(i,h[0]) bit.add(i,1);
    vector<vector<int>> obj(H+1);
    REP(i,h[0]) obj[w[i]].emplace_back(i);
    REP(i,w[0]){
        for(auto j : obj[i]) bit.add(j, -1);
        ans -= bit.sum(0, h[i]);
    }
    cout << ans << endl;
}

Codeforces Round #690 (Div. 3)

codeforces.com

A. Favorite Sequence

問題文に書いてあるとおりに前後前後…の順に出力
deque使えば楽だったね
提出コード

void solve(){
    int n;
    cin >> n;
    vector<ll> b(n);
    REP(i,n) cin >> b[i];
    vector<int> ans;
    REP(i,n/2){
        ans.emplace_back(b[i]);
        ans.emplace_back(b[n-i-1]);
    }
    if(n & 1) ans.emplace_back(b[n/2]);
    REP(i,n) cout << ans[i] << " ";
    cout << endl;
}

B. Last Year's Substring

先頭からのsubstringと後ろからのsubstringで2020を作れるかを判定する
提出コード

void solve(){
    int n;
    string s;
    cin >> n >> s;
    if(n < 4){
        cout << "NO" << endl;
        return;
    }

    for(int i=0;i<=4;i++){
        string t = s.substr(0,i);
        t += s.substr(n-4+i, 4-i);
        if(t == "2020"){
            cout << "YES" << endl;
            return;
        }
    }
    cout << "NO" << endl;
}

C. Unique Number

よくわからないので手元で計算して埋め込んだ
和が大きくなるように貪欲に作っていって、出来た文字列をソートすればいい
提出コード

vector<ll> mi = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 19, 29, 39, 49, 59, 69, 79, 89, 189, 289, 389, 489, 589, 689,789, 1789, 2789, 3789, 4789, 5789, 6789, 16789, 26789, 36789, 46789, 56789, 156789, 256789,356789, 456789, 1456789, 2456789, 3456789, 13456789, 23456789, 123456789, 1000000000000000000, 1000000000000000000, 1000000000000000000, 1000000000000000000, 1000000000000000000};
void solve(){
    int x;
    cin >> x;
    ll ans = mi[x];
    if(ans == LINF) ans = -1;
    cout << ans << endl;
}

D. Number into Sequence

操作を行って最終的に残る数を$ x $とすると、$ x, x, x, ... $のように$ x $が連続したものになる
$ a $の総和は不変のため、上記のような$ x $の候補は総和の約数になる
よってこの約数それぞれに対し、操作が可能かどうかと可能ならその操作回数を調べればいい
提出コード

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    int sum = 0;
    REP(i,n){
        cin >> a[i];
        sum += a[i];
    }
    auto res = divisor(sum);
    ll ans = n-1;
    for(auto x : res){
        int cur = 0;
        REP(i,n){
            cur += a[i];
            if(cur == x) cur = 0;
            else if(cur > x){
                cur = -1;
                break;
            }
        }
        if(cur == 0){
            chmin(ans, n-sum/x);
        }
    }
    cout << ans << endl;
}

E2. Close Tuples (hard version)

数列をソートして考える
このとき最小値を固定してその組み合わせの数を加算していく
$ a_i $を最小値として固定したとき、$ a_i + a_r \leq k $を満たす一番右端のindexを$ r $とすると、$ r - i - 1 $個の中から$ m - 1 $個選ぶ組み合わせの数になるので各$ i $に対して$ _{r - i - 1} C _{m - 1} $の総和を求めればいい
提出コード

void solve(){
    int n, m, k;
    cin >> n >> m >> k;
    vector<int> a(n);
    REP(i,n) cin >> a[i];
    sort(a.begin(), a.end());

    mint ans = 0;
    REP(i,n){
        int t = upper_bound(a.begin(), a.end(), a[i]+k) - a.begin();
        t -= (i + 1);
        if(t >= m-1) ans += bc.com(t, m-1);
    }
    cout << ans << endl;
}

F. The Treasure of The Segments

区間$ [ l_i, r_i] $がいくつの区間を覆っているかは、全ての区間の個数から$ r_j \lt l_i $を満たす個数と$ r_i \lt l_k $を満たす個数を引いたものになる
これは累積和を使えば計算できるが、値が大きいので座圧する必要がある
提出コード

void solve(){
    int n;
    cin >> n;
    vector<int> l(n), r(n);
    Compress<int> cmp;
    REP(i,n){
        cin >> l[i] >> r[i];
        cmp.add(l[i]);
        cmp.add(r[i]);
    }
    cmp.add(0);
    cmp.add(INF+10);
    cmp.build();
    int sz = cmp.size();
    vector<int> cumL(sz+1), cumR(sz+1);
    REP(i,n){
        cumL[cmp.get(r[i])]++;
        cumR[cmp.get(l[i])]++;
    }
    REP(i,sz) cumL[i+1] += cumL[i];
    for(int i=sz-1;i>0;i--) cumR[i-1] += cumR[i];
    int ans = 0;
    REP(i,n){
        chmax(ans, n - cumL[cmp.get(l[i])-1] - cumR[cmp.get(r[i])+1]);
    }
    cout << n - ans << endl;
}