CrCPC 2024 알고리즘 솔루션 가이드

문제 A: 인공지능의 종료 시나리오

이 문제는 상태 간의 전이를 효율적으로 관리하는 것이 핵심입니다. 주어진 값들의 분포를 압축하여 중복을 제거하고 (좌표 압축), 각 단계마다 가능한 최소 이동 횟수를 계산합니다. 전체적인 시간 복잡도는 로그 스케일을 가지므로 \(O(N \log N)\) 입니다.

// 참조 구현 코드
#include <bits/stdc++.h>
using namespace std;

const int MAX_VAL = 1000005;
int target_count[MAX_VAL];
vector<int> compressed_values;

void solve_a() {
    int n, k;
    cin >> n >> k;
    vector<int> input_data(n);
    for(int i=0; i<n; ++i) {
        cin >> input_data[i];
        compressed_values.push_back(input_data[i]);
    }
    
    sort(compressed_values.begin(), compressed_values.end());
    compressed_values.erase(unique(compressed_values.begin(), compressed_values.end()), compressed_values.end());
    
    auto get_id = [&](int val) {
        return lower_bound(compressed_values.begin(), compressed_values.end(), val) - compressed_values.begin();
    };
    
    if (compressed_values.size() < k) {
        cout << 0 << "\n";
        return;
    }
    
    int current_limit = k;
    int min_threshold = 0;
    for(int i=0; i<n; ++i) {
        int id = get_id(input_data[i]);
        if (target_count[id] == min_threshold) {
            current_limit--;
            if (current_limit == 0) {
                min_threshold++;
                current_limit = k - 1;
                target_count[id] = min_threshold + 1;
            } else {
                target_count[id] = min_threshold + 1;
            }
        }
    }
    cout << min_threshold << "\n";
}

문제 B: 기본 수학적 연산

큰 수 \(B\) 를 진수로 간주할 때, 식의 좌변은 \(B\) 가 커질수록 자리수 간 매칭이 줄어듭니다. 이 성질은 단조성을 가지므로, 범위를 좁히면서 최적의 \(B\) 값을 찾는 이진 탐색을 적용합니다. 제곱과 덧셈의 조합을 계산할 때 오버플로우를 방지해야 하며, 최대값 기반 검증이 필요합니다.

// 참조 구현 코드
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int MAX_LEN = 2005;
const ll LIMIT = 2e18;
ll poly_res[MAX_LEN];
int coef_a[MAX_LEN], coef_b[MAX_LEN], coef_c[MAX_LEN];

bool check(ll base) {
    fill(poly_res, poly_res + MAX_LEN, 0);
    int deg_a = /* input size */, deg_b = /* input size */;
    // 다항식 곱셈 및 캐리 처리 로직
    // ...
    // 결과와 타겟 계수 비교
}

void solve_b() {
    int n, m, k;
    cin >> n >> m >> k;
    int mx_val = 0;
    for(int i=0; i<n; ++i) { cin >> coef_a[i]; mx_val = max(mx_val, coef_a[i]); }
    for(int i=0; i<m; ++i) { cin >> coef_b[i]; mx_val = max(mx_val, coef_b[i]); }
    for(int i=0; i<k; ++i) { cin >> coef_c[i]; mx_val = max(mx_val, coef_c[i]); }
    
    ll left = mx_val + 1, right = LIMIT, ans = -1;
    while(left <= right) {
        ll mid = left + (right - left) / 2;
        // 검증 로직 실행
        bool ok = true; 
        // 조건 확인 후 범위 갱신
        if(ok) { ans = mid; right = mid - 1; }
        else left = mid + 1;
    }
    cout << (ans == -1 ? "impossible" : to_string(ans)) << "\n";
}

문제 C: 경로 최적화 공사

두 지점 간의 거리와 추가 비용을 고려한 동적 프로그래밍 (DP) 접근법입니다. 각 점에서 다음 점으로 이동할 때 각도를 기준으로 구간 쿼리를 수행하며, 페닉트리 (BIT) 를 사용하여 구간 합을 효율적으로 업데이트합니다. 이는 \(O(N^2 \log N)\) 에 해결 가능합니다.

// 참조 구현 코드
struct Point { double x, y; };
struct BitTree {
    int tree[1505];
    void update(int idx, int val) {
        for(; idx<=1500; idx+=idx&-idx) tree[idx]+=val;
    }
    int query(int idx) {
        int res=0;
        for(; idx>0; idx-=idx&-idx) res+=tree[idx];
        return res;
    }
};

double dist(Point p1, Point p2) {
    return hypot(p1.x-p2.x, p1.y-p2.y);
}

void solve_c() {
    int n; double cost_per_kg;
    cin >> n >> cost_per_kg;
    vector<Point> locs(n+1);
    for(int i=1; i<=n; ++i) cin >> locs[i].x >> locs[i].y;
    
    // dp[state][direction] 초기화 및 전이 로직
    // 각도 정렬 및 BIT 활용 최적화
    // ...
    // 최종 결과 출력
}

문제 D: 순열 재정렬

순열의 상태를 변화시키는操作的인 경우의 수가 많지만, 목표 도달 단계가 작습니다 (최대 6). 따라서 양방향 BFS 를 활용하여 중간에서 만나게 됩니다. 순열을 Cantor 확장법을 통해 정수로 인코딩하여 방문 집합에 저장합니다. 그래프 탐색의 효율성을 위해 팩토리얼 번호 시스템을 사용합니다.

// 참조 구현 코드
vector<int> perm_to_rank(const vector<int>& p) {
    // 랭킹 계산 로직
}

vector<int> rank_to_perm(int rank, int n) {
    // 랭킹 복원 로직
}

void solve_d() {
    int n; cin >> n;
    vector<int> start_state(n), target_state(n);
    // 입력 처리
    
    unordered_map dist_forward, dist_backward;
    queue<int> q_front, q_back;
    
    // 양방향 검색 시작
    // ...
    // 최단 거리 계산 및 출력
}

문제 E: 확률 기반 추측

무작위 배열에서 특정 값을 맞출 확률을 구하는 문제입니다. 최댓값의 분포를 이용하면, '최댓값이 X 이하인 경우'와 '최댓값이 Y 이하인 경우'의 차이를 통해 정확히 X 일 확률을 유도할 수 있습니다. 이를 합산 공식으로 변환하면 \(k\) 제곱합 문제로 귀결됩니다.

// 참조 구현 코드
ll power(ll base, ll exp) {
    ll res = 1;
    while(exp) { if(exp&1) res=res*base%M; base=base*base%M; exp>>=1; }
    return res;
}

void solve_e() {
    int n, k; cin >> n >> k;
    // 포함배clusion 원리와 조화수 급수 활용
    // 조합론적 항 전개
    // ...
    // 모듈러 연산 적용 후 출력
}

문제 F: 고유 ID 할당

이미 사용된 숫자들의 패리티 (홀짝) 를 세어봅니다. 가장 빈도가 높은 패리티에 속하지 않는 가장 작은 숫자를 찾거나, 그렇지 않은 경우 해당 패리티의 미사용 숫자를 찾습니다. 단순한 시뮬레이션으로 해결 가능합니다.

// 참조 구현 코드
void solve_f() {
    int n; cin >> n;
    set<int> used_numbers;
    int odd_cnt = 0, even_cnt = 0;
    for(int i=0; i> val;
        used_numbers.insert(val);
        if(val%2) odd_cnt++; else even_cnt++;
    }
    
    int preferred_parity = (even_cnt > odd_cnt) ? 1 : 0;
    // 후보 탐색 및 출력
}

문제 G: 그리드 경로 기대값

흰색 칸에서의 행동 패턴을 분류하여 두 가지 유형으로 나눕니다. 각 유형에 대해 목표지점으로 바로 갈지 다시 흰색 칸을 방문할지 결정하는 임계값이 존재하며, 이 함수는 단봉형 (unimodal) 입니다. 따라서 삼분 탐색을 두 번 수행하여 최소 기대값을 찾습니다.

// 참조 구현 코드
Fraction calculate_expectation(int type_idx, int count) {
    // 분수 구조체 정의
    // 기댓값 수식 계산
}

void solve_g() {
    int rows, cols; cin >> rows >> cols;
    vector<string> grid(rows);
    // BFS 로 백워드 거리 계산
    // 타입 분류 및 누적합 배열 구성
    // 삼분 탐색 수행
    // ...
}

문제 H: 산길 네트워크

두 가지 다른 방향성 그래프를 구축하고, 목적지에서의 거리를 기반으로 엣지를 생성합니다. 이후 최장 경로를 찾고 사이클이 있는지 확인하는 SPFA 알고리즘 변형을 사용합니다. 무한 루프 감지를 위해 방문 횟수를 카운트합니다.

// 참조 구현 코드
void solve_h() {
    int n, start_node, end_node; cin >> n >> start_node >> end_node;
    // 그래프 구축 로직 (정방향, 역방향 가중치 분석)
    // 스파피 (SPFA) 기반 최장 경로 탐색
    // 음수 사이클 또는 포지티브 사이클 체크
    // ...
}

문제 I: 순위 판정 시스템

제출 시간 문자열을 파싱하여 총 누적 시간을 초 단위로 변환합니다. 킬러 (AC) 개수, 패널티 시간, 팀 이름의 사전 순서를 기준으로 비교하여 참가자의 최종 랭킹을 도출합니다. 직관적인 시뮬레이션 로직이 요구됩니다.

// 참조 구현 코드
long long parse_time(string s) {
    // HH:MM:SS 포맷 파싱
    // 0-index 기준 오프셋 적용
}

void solve_i() {
    int n_teams; cin >> n_teams;
    // 데이터 읽기 및 정규화
    // 사용자 정의 비교함수를 사용한 랭킹 계산
    // ...
}

문제 J: 구역 이동 비용

주어진 구간들을 모두 커버하기 위한 최적 경로는 최소 좌표에서 최대 좌표로 이동하는 과정에서 발생합니다. 주요 이동 방향 (오른쪽 또는 왼쪽) 을 고정하고, 반대 방향으로 돌아야 하는 구간들에 대한 추가 비용을 최소화하는 문제를贪婪法으로 풉니다.

// 참조 구현 코드
void solve_j() {
    int range_limit, num_segments; cin >> range_limit >> num_segments;
    vector> segs(num_segments);
    // 구간 입력
    // L, R 계산 및 그리디 선택
    // ...
}

문제 K: 트리 충돌 검증

사전 순서 트라이 (Trie) 를 구축한 뒤, 동시에 두 노드를 이동시키며 끝나는 위치가 겹치는지 확인합니다. 만약 서로 다른 단어 경로가 동일한 접두사에서 종료되거나 충돌한다면 조건을 만족하지 못함을 판별합니다. 깊이 우선 탐색 (DFS) 을 통해 재귀적으로 검증합니다.

// 참조 구현 코드
struct Node {
    int next[26] = {0};
    bool is_end = false;
} trie[MAX_NODES];

bool validate_collision(int u, int v) {
    if(u==v && trie[u].is_end) return true; // 충돌
    if(trie[u].is_end && trie[v].is_end) return true;
    // 자식 노드 재귀 호출
    // ...
}

void solve_k() {
    // 트라이 구축
    // 충돌 여부 DFS
    // 결과输出
}

태그: CrCPC CompetitiveProgramming algorithm C++ GraphTheory

6월 5일 01:01에 게시됨