けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

JOI 一次予選 2020 (第 3 回) B - キャピタリゼーション (7Q, 難易度 2)

for 文で隣接する要素を見ながら処理していく系の問題

問題概要

長さ  N の文字列  S が与えられる。

 S を左から見ていき、文字列 "joi" が含まれるならば、これを "JOI" に変換していく処理を繰り返す。操作後の文字列を出力せよ。

解法

次のように考えるとよいでしょう。 S i = 0, 1, 2, \dots 文字目を順に見ていき、もし

  •  S i 文字目が 'j'
  •  S i + 1 文字目が 'o'
  •  S i + 2 文字目が 'i'

であるならば、これらをそれぞれ 'J', 'O', 'I' にしていきます。

コード

#include <bits/stdc++.h>
using namespace std;

int main() {
    int N;
    string S;
    cin >> N >> S;

    for (int i = 0; i + 2 < N; ++i) {
        if (S[i] == 'j' && S[i+1] == 'o' && S[i+2] == 'i') {
            S[i] = 'J';
            S[i+1] = 'O';
            S[i+2] = 'I';
        }
    }
    cout << S << endl;
}

JOI 一次予選 2020 (第 3 回) A - X に最も近い値 (7Q, 難易度 2)

いろんな解法がある!

問題概要

 L 以上  R 以下の整数のうち、 X との差の絶対値が最も小さいものを求めよ。

制約

  •  1 \le X \le 10^{5}
  •  1 \le L \le R \le 10^{5}

解法 (1):for

一番確実な方法は、for 文を用いて、 x = L, L+1, \dots, R をすべて調べることだと思われます。 |x - X| が最小となるような  x を求めればよいでしょう。

コード

#include <bits/stdc++.h>
using namespace std;

int main() {
    int X, L, R;
    cin >> X >> L >> R;
    
    int min_value = 200000;  // 十分大きな値で初期化
    int min_x = -1;
    for (int x = L; x <= R; ++x) {
        int diff = abs(x - X);  // x と X との差
        if (diff < min_value) {
            min_value = diff;
            min_x = x;
        }
    }
    cout << min_x << endl;
}

 

解法 (2):場合分けする

数学的に処理してみましょう。

まず、 L \le X \le R のときは、 x = X のとき、 x X の差は 0 となります。これが明らかに最小です。

次に、 X \lt L のときは、 x = L のとき、 x X の差は  L - X となり、これが最小です。

最後に、 R \lt X のときは、 x = R のとき、 x X の差は  X - R となり、これが最小です。

コード

#include <bits/stdc++.h>
using namespace std;

int main() {
    int X, L, R;
    cin >> X >> L >> R;
    
    if (X < L) cout << L << endl;
    else if (R < X) cout << R << endl;
    else cout << X << endl;
}

JOI 一次予選 2020 (第 2 回) C - 最頻値 (6Q, 難易度 3)

集計処理を練習しよう!

問題概要

 1 以上  M 以下の整数からなる、長さ  N の数列  A_{1}, A_{2}, \dots, A_{N} が与えられる。

長さ  M の新たな数列  B_{1}, B_{2}, \dots, B_{M} を次のように定義する。

  •  1 \le j \le M に対して、 B_{j} を、 A_{i} = j を満たす  i の個数

 B_{1}, B_{2}, \dots, B_{M} の最大値を求めよ。

制約

  •  1 \le N, M \le 100

解法

問題の意味を解釈するのが難しいかもしれないですね。たとえば

  •  A = (4, 2, 1, 2, 2, 5, 4, 7)

について考えてみましょう。1 は 1 個、2 は 3 個、4 は 2 個、5 は 1 個、7 は 1 個あり、このうち最も多いのは 2 の 3 個ですので、答えは「3」です。

これを実装するためには、問題文中に書いてある通り、数列  B を求めるとよいでしょう。次のように実装できます。

vector<int> B(M+1, 0);  // M まで確保する
for (int i = 0; i < N; ++i) ++B[A[i]];  // A[i] の分を 1 個増やす

これが求められたあとは、 B の最大値を求めればよいでしょう。

コード

#include <bits/stdc++.h>
using namespace std;

int main() {
    int N, M;
    cin >> N >> M;
    vector<int> A(N), B(M + 1, 0);
    for (int i = 0; i < N; ++i) {
        cin >> A[i];
        ++B[A[i]];
    }
    
    // B の最大値を求める
    int res = 0;
    for (int i = 1; i <= M; ++i) res = max(res, B[i]);
    cout << res << endl;
}

JOI 一次予選 2020 (第 2 回) B - 文字列の反転 (7Q, 難易度 2)

reverse する系の問題

問題概要

長さ  N の文字列  S について、 A 文字目から  B 文字目までを反転して得られる文字列を出力せよ。

制約

  •  1 \le A \le B \le N \le 200

解法

C++ でプログラムを書くときは、通常文字列は 0 始まりですので、 A, B からあらかじめ 1 引いておきます。

その後は、次のように実装すればよいでしょう。


  •  S 0 文字目から、 A-1 文字目までを出力する
  •  S B 文字目から、 A 文字目までを降順に出力する
  •  S B+1 文字目から、 N-1 文字目までを出力する

for 文を用いて実装できます。降順に出力するときは、添字 i をデクリメント (--i というように) していきます。

コード

#include <bits/stdc++.h>
using namespace std;

int main() {
    int N, A, B;
    string S;
    cin >> N >> A >> B >> S;
    
    --A, --B;
    for (int i = 0; i < A; ++i) cout << S[i];
    for (int i = B; i >= A; --i) cout << S[i];
    for (int i = B+1; i < N; ++i) cout << S[i];
    cout << endl;
}

JOI 一次予選 2020 (第 1 回) C - マージ (5Q, 難易度 3)

マージソートのマージの部分を実装する問題ですね。
添字を順に進めていくような処理を実装します! この実装は、のちにしゃくとり法を学ぶ際の参考にもなります。

問題概要

制約

  •  1 \le N, M \le 500

解法

問題文が複雑で、何をすればいいのかを理解するのが大変かもしれないですね。

一般に、数列  A, B の先頭の要素を削除していく処理は実装が大変です (なお、数列を deque で実装するとか、数列を reverse して末尾から削除していくような実装をするなどの別解はあります)。

そこで、代わりに、数列  A 上の添字  i と、数列  B 上の添字  j を管理することにします。これらは「今この添字のある位置が疑似的に数列の先頭になっています」を表しています。

たとえば、上図のような状況を考えます。数列  A の左 2 個 (2, 4) と、数列  B の左 1 個 (3) がすでに削除されて、数列  C に格納されている状態とします。

次のステップでは、数列  A の真の先頭の要素 (疑似的に値 A[2] = 7) と、数列  B の先頭の要素 (疑似的に値 B[1] = 8) とを比較します。A[2] の方が小さいので、A[2] を数列  C に push して、 A の添字  i を 1 進めます。

このような処理を、添字  i, j がそれぞれ数列  A, B の終端に到達するまで繰り返せばよいでしょう。

コード

#include <bits/stdc++.h>
using namespace std;

int main() {
    int N, M;
    cin >> N >> M;
    vector<int> A(N), B(M);
    for (int i = 0; i < N; ++i) cin >> A[i];
    for (int i = 0; i < M; ++i) cin >> B[i];
    
    vector<int> C;
    int i = 0, j = 0;  // A 側の先頭の添字と B 側の先頭の添字
    while (i < N || j < M) {
        if (i == N) {
            // A 側が終端に達していたら B 側を入れて、B 側の添字 j を進める
            C.push_back(B[j]);
            ++j;
        } else if (j == M) {
            // B 側が終端に達していたら A 側を入れて、A 側の添字 i を進める
            C.push_back(A[i]);
            ++i;
        } else if (A[i] < B[j]) {
            // A の方が小さいとき、A 側を入れて、A 側の添字 i を進める
            C.push_back(A[i]);
            ++i;
        } else {
            // B の方が小さいとき、B 側を入れて、B 側の添字 j を進める
            C.push_back(B[j]);
            ++j;
        }
    }
    
    // 出力
    for (int i = 0; i < N + M; ++i) cout << C[i] << endl;
}