AtCoder Beginner Contest 357 문제 분석 및 풀이

A - 손 소독하기

N명의 외계인이 순차적으로 손을 소독하려고 합니다. 각 외계인은 H_i개의 손을 가지고 있으며, 전체를 소독해야 합니다. 소독제는 총 M회 사용할 수 있습니다. 한 외계인이 소독을 할 때 필요한 양만큼만 사용하며, 부족하면 남은 양만 소모합니다. 모든 손을 소독한 외계인의 수를 구하세요.

단순히 앞에서부터 순회하면서 소독제 잔량을 갱신하고, 소진되기 전까지 카운트를 증가시키면 됩니다.

#include <iostream>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    int count = 0;
    for (int i = 0; i < n; ++i) {
        int hands;
        cin >> hands;
        if (m >= hands) {
            count++;
            m -= hands;
        } else {
            m = 0;
        }
    }
    cout << count;
    return 0;
}

B - 대소문자 변환

영문자로 구성된 문자열 S가 주어집니다. 문자열 길이는 홀수입니다. 대문자의 개수가 소문자보다 많으면 전체를 대문자로, 그렇지 않으면 전체를 소문자로 변환하여 출력하세요.

각 문자를 순회하며 대소문자 개수를 세고, 조건에 따라 변환 여부를 결정합니다.

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

int main() {
    string s;
    cin >> s;
    int lower = 0, upper = 0;
    for (char c : s) {
        if (islower(c)) lower++;
        else upper++;
    }
    for (char c : s) {
        if (lower > upper) {
            cout << (char)tolower(c);
        } else {
            cout << (char)toupper(c);
        }
    }
    return 0;
}

C - 시에르핀스키 카펫

K차 시에르핀스키 카펫은 다음과 같이 정의됩니다:

  • K=0: 1×1 검은색 격자
  • K≥1: 3^K × 3^K 격자를 9등분했을 때, 중앙은 모두 흰색이고 나머지 8개 블록은 (K-1)차 카펫입니다.

N이 주어질 때, N차 카펫을 출력하세요.

반복적으로 확장하는 방식으로 구현할 수 있습니다. 초기 3×3 패턴에서 시작해, 단계별로 크기를 3배 늘리며 중앙을 제외한 블록을 복사합니다.

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

int main() {
    int n;
    cin >> n;
    vector<string> carpet = {"###", "#.#", "###"};
    
    for (int k = 1; k <= n; ++k) {
        vector<string> next(3 * carpet.size(), string(3 * carpet[0].size(), '.'));
        int block = carpet.size();
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                if (i == 1 && j == 1) continue;
                for (int x = 0; x < block; ++x) {
                    for (int y = 0; y < block; ++y) {
                        next[i*block + x][j*block + y] = carpet[x][y];
                    }
                }
            }
        }
        carpet = move(next);
    }
    
    for (const auto& row : carpet) {
        cout << row << '\n';
    }
    return 0;
}

D - 반복 숫자 나머지 계산

V_N은 숫자 N을 N번 이어붙인 정수입니다. 예를 들어 V_3 = 333, V_10 = 1010...10 (10번). V_N mod 998244353을 구하세요.

V_N은 등비수열 합으로 표현할 수 있습니다. N이 d자릿수일 때, V_N = N × (10^{d×N} - 1) / (10^d - 1). 모듈러 역원과 빠른 거듭제곱을 이용해 계산합니다. 지수 부분은 오일러 정리에 따라 mod-1로 축소합니다.

#include <iostream>
#include <string>
using namespace std;
typedef long long ll;

const ll MOD = 998244353;

ll powmod(ll base, ll exp, ll mod) {
    ll result = 1;
    while (exp) {
        if (exp & 1) result = result * base % mod;
        base = base * base % mod;
        exp >>= 1;
    }
    return result;
}

int main() {
    string num;
    cin >> num;
    ll n = 0, len = num.size();
    ll power = 1;
    for (char c : num) {
        n = (n * 10 + (c - '0')) % MOD;
        power = power * 10 % MOD;
    }
    
    ll numerator = (powmod(power, n % (MOD-1), MOD) - 1 + MOD) % MOD;
    numerator = numerator * n % MOD;
    ll denominator = (power - 1 + MOD) % MOD;
    ll inv_denom = powmod(denominator, MOD-2, MOD);
    cout << numerator * inv_denom % MOD;
    return 0;
}

E - 기능 그래프 도달 가능성

N개의 정점이 있고, 각 정점 i는 a_i로 가는 유일한 간선을 가집니다. 이때, u에서 v로 도달 가능한 쌍 (u,v)의 수를 구하세요.

이 구조는 여러 개의 사이클과 그에 붙은 트리(기저 고리 나무)로 구성됩니다. 먼저 DFS를 통해 사이클을 탐지하고, 사이클 내 정점들은 서로 도달 가능하므로 각각 사이클 크기만큼 기여합니다. 트리 부분은 위상 정렬이나 메모이제이션으로 처리하며, 각 노드는 자식의 값 + 1을 갖습니다.

#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;

int main() {
    int n;
    cin >> n;
    vector<int> to(n+1), visited(n+1), value(n+1);
    for (int i = 1; i <= n; ++i) cin >> to[i];

    for (int i = 1; i <= n; ++i) {
        if (visited[i]) continue;
        int cur = i;
        while (!visited[cur]) {
            visited[cur] = i;
            cur = to[cur];
        }
        if (visited[cur] == i) { // 새로운 사이클 발견
            vector<int> cycle;
            int node = cur;
            do {
                cycle.push_back(node);
                node = to[node];
            } while (node != cur);
            for (int v : cycle) value[v] = cycle.size();
        }
    }

    function<int(int)> dfs = [&](int u) -> int {
        if (value[u]) return value[u];
        return value[u] = dfs(to[u]) + 1;
    };

    ll total = 0;
    for (int i = 1; i <= n; ++i) total += dfs(i);
    cout << total;
    return 0;
}

F - 두 수열 쿼리

두 수열 A와 B가 주어지고, 다음 세 가지 쿼리를 처리하세요:

  • 1 l r x: A[l..r]에 x 더하기
  • 2 l r x: B[l..r]에 x 더하기
  • 3 l r: Σ(A_i × B_i) mod 998244353

세그먼트 트리를 사용하며, 각 노드는 ΣA, ΣB, Σ(A×B) 값을 저장합니다. 레이지 프로퍼게이션으로 두 가지 업데이트를 관리합니다. A에 k를 더하면 Σ(A×B)는 k × ΣB만큼 증가하며, B에 대해서도 마찬가지입니다.

#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;

const int MAX = 2e5+5;
const ll MOD = 998244353;

struct Node {
    ll sum_a, sum_b, sum_ab, lazy_a, lazy_b;
} tree[MAX << 2];

void pushup(int u) {
    tree[u].sum_a = (tree[u<<1].sum_a + tree[u<<1|1].sum_a) % MOD;
    tree[u].sum_b = (tree[u<<1].sum_b + tree[u<<1|1].sum_b) % MOD;
    tree[u].sum_ab = (tree[u<<1].sum_ab + tree[u<<1|1].sum_ab) % MOD;
}

void apply(int u, int l, int r, ll add_a, ll add_b) {
    int len = r - l + 1;
    tree[u].sum_ab = (tree[u].sum_ab + add_a * tree[u].sum_b % MOD + 
                      add_b * tree[u].sum_a % MOD + add_a * add_b % MOD * len) % MOD;
    tree[u].sum_a = (tree[u].sum_a + add_a * len) % MOD;
    tree[u].sum_b = (tree[u].sum_b + add_b * len) % MOD;
    tree[u].lazy_a = (tree[u].lazy_a + add_a) % MOD;
    tree[u].lazy_b = (tree[u].lazy_b + add_b) % MOD;
}

void pushdown(int u, int l, int r) {
    if (tree[u].lazy_a == 0 && tree[u].lazy_b == 0) return;
    int mid = (l + r) >> 1;
    apply(u<<1, l, mid, tree[u].lazy_a, tree[u].lazy_b);
    apply(u<<1|1, mid+1, r, tree[u].lazy_a, tree[u].lazy_b);
    tree[u].lazy_a = tree[u].lazy_b = 0;
}

void build(int u, int l, int r, vector<ll>& A, vector<ll>& B) {
    if (l == r) {
        tree[u] = {A[l], B[l], (A[l]*B[l])%MOD, 0, 0};
    } else {
        int mid = (l + r) >> 1;
        build(u<<1, l, mid, A, B);
        build(u<<1|1, mid+1, r, A, B);
        pushup(u);
    }
}

void update(int u, int l, int r, int ul, int ur, ll add_a, ll add_b) {
    if (ul <= l && r <= ur) {
        apply(u, l, r, add_a, add_b);
        return;
    }
    pushdown(u, l, r);
    int mid = (l + r) >> 1;
    if (ul <= mid) update(u<<1, l, mid, ul, ur, add_a, add_b);
    if (ur > mid) update(u<<1|1, mid+1, r, ul, ur, add_a, add_b);
    pushup(u);
}

ll query(int u, int l, int r, int ql, int qr) {
    if (ql <= l && r <= qr) return tree[u].sum_ab;
    pushdown(u, l, r);
    int mid = (l + r) >> 1;
    ll res = 0;
    if (ql <= mid) res = (res + query(u<<1, l, mid, ql, qr)) % MOD;
    if (qr > mid) res = (res + query(u<<1|1, mid+1, r, ql, qr)) % MOD;
    return res;
}

태그: AtCoder C++ 알고리즘 세그먼트 트리 모듈러 연산

6월 13일 19:05에 게시됨