競技プログラミング練習問題集:基礎アルゴリズムと実装の解説

幾何学的面積計算問題

解法の指針

指定された矩形領域内で、頂点座標から特定の三角形と台形の面積を減算して目的の面積を求めます。

#include <iostream>
using namespace std;

int calculate_shaded_area(int x, int y) {
    const int total_area = 5000;
    int triangle_area = (100 - x) * 25;
    int trapezoid_area = y * 25;
    return total_area - triangle_area - trapezoid_area;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int coordinate_x, coordinate_y;
    cin >> coordinate_x >> coordinate_y;
    cout << calculate_shaded_area(coordinate_x, coordinate_y) << endl;
    return 0;
}

固定文字列出力

解法の指針

指定された文言をそのまま出力します。

To iterate is human, to recurse divine.

連絡先データの管理と表示

解法の指針

個人情報の配列を読み込み、クエリに基づいて特定のフォーマットで表示します。無効なインデックスにはエラーメッセージを出力します。

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

struct ContactEntry {
    string name;
    string address_line1;
    string address_line2;
    string mobile_number;
    string fixed_number;
};

int main() {
    int entry_count;
    cin >> entry_count;
    vector<ContactEntry> directory(entry_count);
    for (auto &record : directory) {
        cin >> record.name >> record.mobile_number >> record.fixed_number >> record.address_line1 >> record.address_line2;
    }
    int query_count;
    cin >> query_count;
    while (query_count--) {
        int index;
        cin >> index;
        if (index < 0 || index >= entry_count) {
            cout << "Not Found\n";
        } else {
            const auto &record = directory[index];
            cout << record.name << " " << record.address_line1 << " " << record.address_line2 << " " << record.fixed_number << " " << record.mobile_number << "\n";
        }
    }
    return 0;
}

四則演算の基本

解法の指針

二つの整数を受け取り、和・差・積・商を指定フォーマットで出力します。割り切れない場合は小数点以下2桁で表示します。

#include <iomanip>
#include <iostream>
using namespace std;

void perform_arithmetic_operations(int num1, int num2) {
    cout << num1 << " + " << num2 << " = " << num1 + num2 << endl;
    cout << num1 << " - " << num2 << " = " << num1 - num2 << endl;
    cout << num1 << " * " << num2 << " = " << num1 * num2 << endl;
    if (num2 != 0) {
        if (num1 % num2 == 0) {
            cout << num1 << " / " << num2 << " = " << num1 / num2 << endl;
        } else {
            cout << fixed << setprecision(2) << num1 << " / " << num2 << " = " << static_cast<double>(num1) / num2 << endl;
        }
    }
}

int main() {
    int input_a, input_b;
    cin >> input_a >> input_b;
    perform_arithmetic_operations(input_a, input_b);
    return 0;
}

指定された桁数の異なる数字を持つ年を求める

解法の指針

基準年から始めて、年の数字に含まれる異なる数字の数が指定数と一致する最初の年を見つけます。

#include <iostream>
#include <set>
using namespace std;

bool has_required_unique_digits(int year, int required_count) {
    set<int> digit_set;
    if (year < 1000) digit_set.insert(0);
    int temp = year;
    while (temp > 0) {
        digit_set.insert(temp % 10);
        temp /= 10;
    }
    return digit_set.size() == required_count;
}

int main() {
    int base_year, target_unique_count;
    cin >> base_year >> target_unique_count;
    int current_year = base_year;
    while (true) {
        if (has_required_unique_digits(current_year, target_unique_count)) {
            printf("%d %04d\n", current_year - base_year, current_year);
            break;
        }
        current_year++;
    }
    return 0;
}

ケータイ式テンキー入力のシミュレーション

解法の指針

各キーに割り当てられた文字列から、入力シーケンスに基づいて対応する文字を選択します。

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

int main() {
    const vector<string> key_mapping = {
        "0 ",
        "1,.?!",
        "2ABC",
        "3DEF",
        "4GHI",
        "5JKL",
        "6MNO",
        "7PQRS",
        "8TUV",
        "9WXYZ"
    };
    string input_sequence;
    while (cin >> input_sequence) {
        int key_index = input_sequence[0] - '0';
        int press_count = input_sequence.length() - 1;
        press_count %= key_mapping[key_index].length();
        cout << key_mapping[key_index][press_count];
    }
    return 0;
}

螺旋状の正方行列の生成

解法の指針

時計回りの螺旋パターンで行列を埋めていきます。境界と既に埋められたセルをチェックしながら進行方向を変更します。

#include <iomanip>
#include <iostream>
#include <vector>
using namespace std;

vector<vector<int>> generate_spiral_matrix(int dimension) {
    vector<vector<int>> matrix(dimension + 1, vector<int>(dimension + 1, 0));
    int filled_count = 0;
    int current_row = 1, current_col = 0;
    const vector<pair<int, int>> directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    int dir_index = 0;
    while (filled_count < dimension * dimension) {
        auto [dr, dc] = directions[dir_index];
        while (true) {
            int next_row = current_row + dr;
            int next_col = current_col + dc;
            if (next_row < 1 || next_row > dimension || next_col < 1 || next_col > dimension || matrix[next_row][next_col] != 0) {
                break;
            }
            current_row = next_row;
            current_col = next_col;
            matrix[current_row][current_col] = ++filled_count;
        }
        dir_index = (dir_index + 1) % 4;
    }
    return matrix;
}

int main() {
    int n;
    cin >> n;
    auto result_matrix = generate_spiral_matrix(n);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cout << setw(3) << result_matrix[i][j];
        }
        cout << "\n";
    }
    return 0;
}

ネズミ捕獲ゲームの収支シミュレーション

解法の指針

各ターンの行動とそれに伴う感情状態(ハッピー、不機嫌、悲しみ)を管理し、収支を計算します。

#include <iostream>
#include <string>
using namespace std;

int main() {
    string action_sequence;
    cin >> action_sequence;
    int happiness_counter = 0, upset_counter = 0, sadness_counter = 0;
    int total_profit = 0;
    string result_log = "";

    auto decrease_counters = [&]() {
        if (happiness_counter > 0) happiness_counter--;
        if (upset_counter > 0) upset_counter--;
        if (sadness_counter > 0) sadness_counter--;
    };

    for (char action : action_sequence) {
        if (action == 'X') {
            if (happiness_counter > 0 || (upset_counter == 0 && sadness_counter == 0)) {
                result_log += "U";
                upset_counter = 2;
            } else {
                result_log += "-";
            }
        } else if (action == 'T') {
            if (happiness_counter > 0 || (upset_counter == 0 && sadness_counter == 0)) {
                result_log += "D";
                total_profit += 7;
                sadness_counter = 3;
            } else {
                result_log += "-";
            }
        } else if (action == 'C') {
            if (happiness_counter > 0 || (upset_counter == 0 && sadness_counter == 0)) {
                result_log += "!";
                total_profit -= 3;
                happiness_counter = 3;
            } else {
                result_log += "-";
            }
        }
        decrease_counters();
    }
    cout << result_log << "\n" << total_profit << endl;
    return 0;
}

優先度付きメッセージキューの実装

解法の指針

プッシュ操作でメッセージを優先度付きでキューに追加し、ポップ操作で最も優先度の高いメッセージを取り出します。

#include <iostream>
#include <queue>
#include <string>
using namespace std;

int main() {
    int operation_count;
    cin >> operation_count;
    priority_queue<pair<int, string>, vector<pair<int, string>>, greater<pair<int, string>>> msg_queue;
    while (operation_count--) {
        string operation_type;
        cin >> operation_type;
        if (operation_type == "GET") {
            if (msg_queue.empty()) {
                cout << "EMPTY QUEUE!\n";
            } else {
                cout << msg_queue.top().second << "\n";
                msg_queue.pop();
            }
        } else {
            string message_content;
            int priority_value;
            cin >> message_content >> priority_value;
            msg_queue.emplace(priority_value, message_content);
        }
    }
    return 0;
}

ランキングシステムとポイント計算

解法の指針

スコアに基づいてポイントを計算し、ランキングをスコアの降順、同点の場合は名前の昇順でソートして表示します。

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() {
    int participant_count, bonus_threshold, display_limit;
    cin >> participant_count >> bonus_threshold >> display_limit;
    vector<pair<string, int>> records(participant_count);
    int total_points = 0;
    for (auto &[name, score] : records) {
        cin >> name >> score;
        if (score >= bonus_threshold) {
            total_points += 50;
        } else if (score >= 60) {
            total_points += 20;
        }
    }
    cout << total_points << "\n";
    sort(records.begin(), records.end(), [](const auto &a, const auto &b) {
        if (a.second != b.second) return a.second > b.second;
        return a.first < b.first;
    });
    int current_rank = 1;
    for (size_t i = 0; i < records.size(); i++) {
        if (current_rank > display_limit) break;
        cout << current_rank << " " << records[i].first << " " << records[i].second << "\n";
        size_t j = i;
        while (j + 1 < records.size() && records[j + 1].second == records[i].second) {
            j++;
            cout << current_rank << " " << records[j].first << " " << records[j].second << "\n";
        }
        i = j;
        current_rank = j + 2;
    }
    return 0;
}

24ゲーム:数式評価による解法

解法の指針

4つの数字と演算子の全順列・組み合わせを試し、括弧の配置パターンを網羅して24を作る数式を探索します。

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

bool evaluate_expression(const string &expr) {
    stack<double> values;
    stack<char> operators;
    auto apply_operator = [](double a, double b, char op) -> double {
        switch (op) {
            case '+': return a + b;
            case '-': return a - b;
            case '*': return a * b;
            case '/': return a / b;
            default: return 0.0;
        }
    };
    for (size_t i = 0; i < expr.length(); i++) {
        if (expr[i] == ' ') continue;
        if (isdigit(expr[i])) {
            double num = expr[i] - '0';
            while (i + 1 < expr.length() && isdigit(expr[i + 1])) {
                num = num * 10 + (expr[i + 1] - '0');
                i++;
            }
            values.push(num);
        } else if (expr[i] == '(') {
            operators.push('(');
        } else if (expr[i] == ')') {
            while (!operators.empty() && operators.top() != '(') {
                double b = values.top(); values.pop();
                double a = values.top(); values.pop();
                char op = operators.top(); operators.pop();
                values.push(apply_operator(a, b, op));
            }
            operators.pop(); // Remove '('
        } else { // Operator
            while (!operators.empty() && operators.top() != '(') {
                double b = values.top(); values.pop();
                double a = values.top(); values.pop();
                char op = operators.top(); operators.pop();
                values.push(apply_operator(a, b, op));
            }
            operators.push(expr[i]);
        }
    }
    while (!operators.empty()) {
        double b = values.top(); values.pop();
        double a = values.top(); values.pop();
        char op = operators.top(); operators.pop();
        values.push(apply_operator(a, b, op));
    }
    return fabs(values.top() - 24.0) < 1e-9;
}

int main() {
    vector<string> cards(4);
    for (int i = 0; i < 4; i++) cin >> cards[i];
    const string operators = "+-*/";
    sort(cards.begin(), cards.end());
    vector<string> patterns = {
        "(({0}{1}{2}){3}{4}){5}{6}",
        "({0}{1}{2}){3}({4}{5}{6})",
        "{0}{1}({2}{3}({4}{5}{6}))",
        "{0}{1}(({2}{3}{4}){5}{6})",
        "({0}{1}({2}{3}{4})){5}{6}"
    };
    do {
        for (char op1 : operators) {
            for (char op2 : operators) {
                for (char op3 : operators) {
                    for (const string &pattern : patterns) {
                        string expr;
                        size_t idx = 0;
                        int card_idx = 0;
                        char ops[] = {op1, op2, op3};
                        int op_idx = 0;
                        while (idx < pattern.length()) {
                            if (pattern[idx] == '{') {
                                if (pattern[idx + 1] >= '0' && pattern[idx + 1] <= '9') {
                                    expr += cards[card_idx++];
                                } else {
                                    expr += ops[op_idx++];
                                }
                                idx += 3; // Skip {x}
                            } else {
                                expr += pattern[idx++];
                            }
                        }
                        if (evaluate_expression(expr)) {
                            cout << expr << endl;
                            return 0;
                        }
                    }
                }
            }
        }
    } while (next_permutation(cards.begin(), cards.end()));
    cout << "-1\n";
    return 0;
}

前序・中序走査からの鏡像化されたレベル順走査

解法の指針

与えられた前序と中序の走査結果から二分木を再構築し、各レベルのノードを逆順に集約して出力します。

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

void reconstruct_tree(const vector<int> &inorder, const vector<int> &preorder, int pre_start, int in_start, int in_end, int depth, map<int, vector<int>> &level_map) {
    if (in_start > in_end) return;
    int root_val = preorder[pre_start];
    int in_root_idx = in_start;
    while (in_root_idx <= in_end && inorder[in_root_idx] != root_val) in_root_idx++;
    int left_subtree_size = in_root_idx - in_start;
    reconstruct_tree(inorder, preorder, pre_start + 1, in_start, in_root_idx - 1, depth + 1, level_map);
    reconstruct_tree(inorder, preorder, pre_start + left_subtree_size + 1, in_root_idx + 1, in_end, depth + 1, level_map);
    level_map[depth].push_back(root_val);
}

int main() {
    int node_count;
    cin >> node_count;
    vector<int> inorder(node_count), preorder(node_count);
    for (int i = 0; i < node_count; i++) cin >> inorder[i];
    for (int i = 0; i < node_count; i++) cin >> preorder[i];
    map<int, vector<int>> level_nodes;
    reconstruct_tree(inorder, preorder, 0, 0, node_count - 1, 0, level_nodes);
    vector<int> mirrored_output;
    for (int level = 0; level < node_count; level++) {
        if (level_nodes.count(level)) {
            const auto &nodes = level_nodes[level];
            mirrored_output.insert(mirrored_output.end(), nodes.rbegin(), nodes.rend());
        }
    }
    for (size_t i = 0; i < mirrored_output.size(); i++) {
        cout << mirrored_output[i] << (i + 1 == mirrored_output.size() ? "\n" : " ");
    }
    return 0;
}

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

7月15日 16:05 投稿