競技プログラミングの復習と解法まとめ

7月は主に学校の夏期個人戦に参加し、その他の時間は主に学習やゲームに費やした。

上海大学プログラミングコンテスト 2024

上海地区のコンテストで、結果は芳しくなかった。主に実力不足が原因。

問題E - 無線ソフトウェア日

文字の出現頻度をカウントし、必要な文字数の最小値を求める問題。

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

int main() {
    int n;
    cin >> n;
    string s;
    cin >> s;
    int count[256] = {0};
    
    for (int i = 0; i < n; i++) {
        count[tolower(s[i])]++;
    }
    
    int result = 1e9;
    result = min(result, count['s']);
    result = min(result, count['h'] / 2);
    result = min(result, count['a'] / 2);
    result = min(result, count['n']);
    result = min(result, count['g']);
    result = min(result, count['i']);
    
    cout << result << endl;
    return 0;
}

問題J - 最小合成数列

合成数の判定と区間和を用いた最短部分列の探索を行う。

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

const int MAX_N = 1050;
long long prefix_sum[MAX_N];
int arr[MAX_N];

bool is_composite(long long x) {
    if (x < 4) return false;
    for (long long i = 2; i * i <= x; i++) {
        if (x % i == 0) return true;
    }
    return false;
}

void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> arr[i];
        prefix_sum[i] = prefix_sum[i-1] + arr[i];
    }
    
    int min_length = 1e9;
    for (int i = 1; i <= n; i++) {
        for (int j = i; j <= n; j++) {
            if (is_composite(prefix_sum[j] - prefix_sum[i-1])) {
                min_length = min(min_length, j - i + 1);
            }
        }
    }
    
    if (min_length == 1e9) {
        cout << -1 << endl;
    } else {
        cout << min_length << endl;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}

問題A - 無線ネットワーク格子点統計

正方形の頂点を基準に他の頂点を計算し、範囲内にあるかを判定する幾何問題。

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

int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            int count = 0;
            for (int x = 0; x <= n; x++) {
                for (int y = 0; y <= m; y++) {
                    if (x == i && y == j) continue;
                    
                    // 正方形の他の2点を計算
                    int p1 = x - (j - y);
                    int q1 = y + (i - x);
                    int p2 = i - (j - y);
                    int q2 = j + (i - x);
                    
                    int valid_count = 0;
                    if (p1 >= 0 && p1 <= n && q1 >= 0 && q1 <= m) valid_count++;
                    if (p2 >= 0 && p2 <= n && q2 >= 0 && q2 <= m) valid_count++;
                    
                    if (valid_count == 2) count++;
                    
                    // 反対側の正方形
                    p1 = x + (j - y);
                    q1 = y - (i - x);
                    p2 = i + (j - y);
                    q2 = j - (i - x);
                    
                    valid_count = 0;
                    if (p1 >= 0 && p1 <= n && q1 >= 0 && q1 <= m) valid_count++;
                    if (p2 >= 0 && p2 <= n && q2 >= 0 && q2 <= m) valid_count++;
                    
                    if (valid_count == 2) count++;
                }
            }
            printf("%d ", count / 2);
        }
        printf("\n");
    }
    return 0;
}

江西省プログラミングコンテスト 2024

チームでの参加で銅賞を獲得。二人チームでの結果としては良い成績。

問題A - マリアンの絵画学習

基本的な入力処理問題。

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

int main() {
    long long a, b, c;
    cin >> a >> b >> c;
    cout << a + b + c << endl;
    return 0;
}

問題C - 嘘つき

貪欲法を用いた問題。総和が目標値と一致する場合の判定が必要。

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

int main() {
    int n, target;
    cin >> n >> target;
    vector<int> values(n);
    int sum = 0;
    
    for (int i = 0; i < n; i++) {
        cin >> values[i];
        sum += values[i];
    }
    
    if (sum == target) {
        cout << n << endl;
    } else {
        cout << n - 1 << endl;
    }
    return 0;
}

問題K - 魔法の木

指数関数的な増加パターンを持つ問題。繰り返し二乗法を適用。

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

long long power(long long base, long long exp) {
    long long result = 1;
    while (exp > 0) {
        if (exp & 1) result = result * base % MOD;
        base = base * base % MOD;
        exp >>= 1;
    }
    return result;
}

int main() {
    long long n;
    cin >> n;
    cout << power(2, n - 1) << endl;
    return 0;
}

問題G - 5の倍数

大きな数の処理において、桁ごとの計算を行う必要がある。

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

int main() {
    int test_cases;
    cin >> test_cases;
    
    while (test_cases--) {
        int n;
        cin >> n;
        long long sum = 0;
        
        for (int i = 0; i < n; i++) {
            char digit_char, count_char;
            cin >> digit_char >> count_char;
            int count = count_char - '0';
            if (count_char == 'A') count = 10;
            
            sum += (digit_char - '0') * count;
            sum %= 10;
        }
        
        if (sum % 5 == 0) {
            cout << "Yes" << endl;
        } else {
            cout << "No" << endl;
        }
    }
    return 0;
}

問題H - 畳み込み

二次元配列に対する畳み込み演算。効率的な計算のために二次元累積和を使用。

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

const int MAX_SIZE = 1050;
long long matrix[MAX_SIZE][MAX_SIZE];
long long prefix_sum[MAX_SIZE][MAX_SIZE];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int rows, cols, kernel_rows, kernel_cols;
    cin >> rows >> cols >> kernel_rows >> kernel_cols;
    
    for (int i = 1; i <= rows; i++) {
        for (int j = 1; j <= cols; j++) {
            cin >> matrix[i][j];
            prefix_sum[i][j] = prefix_sum[i-1][j] + prefix_sum[i][j-1] 
                             - prefix_sum[i-1][j-1] + matrix[i][j];
        }
    }
    
    int result_rows = rows - kernel_rows + 1;
    int result_cols = cols - kernel_cols + 1;
    long long total = 0;
    
    for (int i = 0; i < result_rows; i++) {
        for (int j = 0; j < result_cols; j++) {
            int x1 = i + 1, y1 = j + 1;
            int x2 = i + kernel_rows, y2 = j + kernel_cols;
            
            long long region_sum = prefix_sum[x2][y2] - prefix_sum[x1-1][y2] 
                                 - prefix_sum[x2][y1-1] + prefix_sum[x1-1][y1-1];
            total += abs(region_sum);
        }
    }
    
    cout << total << endl;
    return 0;
}

牛客練習賽127

難易度が予想以上で苦戦したコンテスト。

問題A - 小紅の最大価値

ソート後の要素の比較により最大値を求める。

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

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    vector<int> arr(n);
    
    for (int i = 0; i < n; i++) {
        scanf("%d", &arr[i]);
    }
    
    sort(arr.begin(), arr.end());
    
    if (arr[0] + k >= arr[n-1]) {
        printf("%d\n", arr[n-2]);
    } else {
        printf("%d\n", arr[n-1]);
    }
    return 0;
}

AtCoder Beginner Contest 361

いくつかの問題で苦戦したが、最終的にレートは安定して上昇。

問題A - 挿入

指定された位置に要素を挿入するシミュレーション問題。

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

int main() {
    int n, pos, value;
    cin >> n >> pos >> value;
    vector<int> arr(n + 1);
    
    for (int i = 1; i <= n; i++) {
        cin >> arr[i];
    }
    
    for (int i = 1; i <= n; i++) {
        cout << arr[i] << " ";
        if (i == pos) {
            cout << value << " ";
        }
    }
    cout << "\n";
    return 0;
}

問題C - 狭くする

ソート後にスライディングウィンドウ法で最小差を求める。

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

int main() {
    int n, keep_count;
    cin >> n >> keep_count;
    int remove_count = n - keep_count;
    vector<int> arr(n);
    
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    
    sort(arr.begin(), arr.end());
    
    int min_diff = 1e9;
    for (int i = 0; i + remove_count - 1 < n; i++) {
        min_diff = min(min_diff, arr[i + remove_count - 1] - arr[i]);
    }
    
    cout << min_diff << endl;
    return 0;
}

問題B - 直方体の交差

三次元空間における直方体の交差判定。

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

int main() {
    int x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4;
    cin >> x1 >> y1 >> z1 >> x2 >> y2 >> z2 >> x3 >> y3 >> z3 >> x4 >> y4 >> z4;
    
    int overlap_count = 0;
    
    if ((x1 <= x3 && x3 < x2) || (x3 <= x1 && x1 < x4)) overlap_count++;
    if ((y1 <= y3 && y3 < y2) || (y3 <= y1 && y1 < y4)) overlap_count++;
    if ((z1 <= z3 && z3 < z2) || (z3 <= z1 && z1 < z4)) overlap_count++;
    
    if (overlap_count == 3) {
        cout << "Yes\n";
    } else {
        cout << "No\n";
    }
    return 0;
}

Codeforces Round #956 (Div. 2)

レート向上に貢献したコンテスト。

問題A - 配列の割り算可能性

条件を満たす配列の構築問題。

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

int main() {
    int test_cases;
    scanf("%d", &test_cases);
    
    while (test_cases--) {
        int n;
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            printf("%d ", i);
        }
        printf("\n");
    }
    return 0;
}

問題C - ケーキを食べる

三つの配列について、それぞれの部分和が条件を満たすかどうかを二分探索で判定。

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

const int MAX_N = 200010;
long long a[MAX_N], b[MAX_N], c[MAX_N];
long long prefix_a[MAX_N], prefix_b[MAX_N], prefix_c[MAX_N];

void solve() {
    int n;
    scanf("%d", &n);
    
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
        prefix_a[i] = prefix_a[i-1] + a[i];
    }
    
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &b[i]);
        prefix_b[i] = prefix_b[i-1] + b[i];
    }
    
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &c[i]);
        prefix_c[i] = prefix_c[i-1] + c[i];
    }
    
    long long total = prefix_a[n];
    long long threshold = (total + 2) / 3;
    
    // 各種パターンのチェック
    for (int pattern = 0; pattern < 6; pattern++) {
        int start_a, end_a, start_b, end_b, start_c, end_c;
        
        switch (pattern) {
            case 0:
                start_a = 1;
                end_a = lower_bound(prefix_a + 1, prefix_a + n + 1, threshold) - prefix_a;
                start_b = end_a + 1;
                long long target_b = prefix_b[end_a] + threshold;
                end_b = lower_bound(prefix_b + 1, prefix_b + n + 1, target_b) - prefix_b;
                start_c = end_b + 1;
                end_c = n;
                break;
            // 他のパターンも同様に実装...
        }
        
        if (end_a <= n && end_b <= n && end_c <= n &&
            prefix_a[end_a] - prefix_a[start_a-1] >= threshold &&
            prefix_b[end_b] - prefix_b[start_b-1] >= threshold &&
            prefix_c[end_c] - prefix_c[start_c-1] >= threshold) {
            printf("%d %d %d %d %d %d\n", start_a, end_a, start_b, end_b, start_c, end_c);
            return;
        }
    }
    
    printf("-1\n");
}

int main() {
    prefix_a[0] = prefix_b[0] = prefix_c[0] = 0;
    int test_cases = 1;
    scanf("%d", &test_cases);
    
    while (test_cases--) {
        solve();
    }
    return 0;
}

問題B - コーナーのねじれ

各行・各列の和の剰余が一致することを確認する問題。

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

const int MAX_SIZE = 510;
char matrix_a[MAX_SIZE][MAX_SIZE], matrix_b[MAX_SIZE][MAX_SIZE];

int main() {
    int test_cases;
    scanf("%d", &test_cases);
    
    while (test_cases--) {
        int rows, cols;
        scanf("%d%d", &rows, &cols);
        
        for (int i = 0; i < rows; i++) {
            scanf("%s", matrix_a[i]);
        }
        
        for (int i = 0; i < rows; i++) {
            scanf("%s", matrix_b[i]);
        }
        
        bool valid = true;
        
        // 行のチェック
        for (int i = 0; i < rows; i++) {
            int sum_a = 0, sum_b = 0;
            for (int j = 0; j < cols; j++) {
                sum_a += matrix_a[i][j] - '0';
                sum_b += matrix_b[i][j] - '0';
            }
            if (sum_a % 3 != sum_b % 3) {
                valid = false;
                break;
            }
        }
        
        if (!valid) {
            printf("No\n");
            continue;
        }
        
        // 列のチェック
        for (int j = 0; j < cols; j++) {
            int sum_a = 0, sum_b = 0;
            for (int i = 0; i < rows; i++) {
                sum_a += matrix_a[i][j] - '0';
                sum_b += matrix_b[i][j] - '0';
            }
            if (sum_a % 3 != sum_b % 3) {
                valid = false;
                break;
            }
        }
        
        if (valid) {
            printf("Yes\n");
        } else {
            printf("No\n");
        }
    }
    return 0;
}

7月上旬のコンテストは以上で終了。下旬は主に牛客の多校合同コンテストに注力する予定。

タグ: 競技プログラミング アルゴリズム データ構造 C++ AtCoder

5月16日 06:11 投稿