AGC005 문제 해설

A - STring

스택을 이용한 시뮬레이션으로 해결합니다. 문자열을 순회하면서 'S'는 스택에 추가하고, 'T'가 등장할 때 스택 상단이 'S'이면 제거합니다. 최종적으로 남은 스택 크기가 정답입니다.

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

int main() {
    string str;
    cin >> str;
    stack<char> stk;
    
    for (char c : str) {
        if (!stk.empty() && stk.top() == 'S' && c == 'T') {
            stk.pop();
        } else {
            stk.push(c);
        }
    }
    
    cout << stk.size();
    return 0;
}

B - Minimum Sum

분할 정복과 세그먼트 트리를 이용해 최소값 위치를 찾습니다. 각 원소가 최소값인 구간의 개수를 계산하여 누적합니다. 구간 [l, r]에서 최소값 위치 p에 대한 점화식은 (p-l+1)*(r-p+1)*arr[p]입니다.

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

struct SegTree {
    vector<int> tree;
    int size;
    
    SegTree(int n) {
        size = 1;
        while (size < n) size *= 2;
        tree.resize(2*size, 0);
    }
    
    void build(vector<int>& arr, int n) {
        for (int i=0; i<n; i++) tree[size+i] = i;
        for (int i=size-1; i>0; i--) {
            int l = tree[2*i], r = tree[2*i+1];
            tree[i] = (arr[l] < arr[r]) ? l : r;
        }
    }
    
    int query(vector<int>& arr, int l, int r, int n) {
        l += size; r += size;
        int res = l;
        while (l <= r) {
            if (l%2 == 1) res = (arr[tree[l]] < arr[res]) ? tree[l] : res;
            if (r%2 == 0) res = (arr[tree[r]] < arr[res]) ? tree[r] : res;
            l = (l+1)/2;
            r = (r-1)/2;
        }
        return res;
    }
};

ll calc(vector<int>& arr, SegTree& st, int l, int r) {
    if (l > r) return 0;
    int p = st.query(arr, l, r, arr.size());
    ll left = calc(arr, st, l, p-1);
    ll right = calc(arr, st, p+1, r);
    return left + right + 1LL*arr[p]*(p-l+1)*(r-p+1);
}

int main() {
    int n;
    cin >> n;
    vector<int> arr(n);
    for (int i=0; i<n; i++) cin >> arr[i];
    
    SegTree st(n);
    st.build(arr, n);
    cout << calc(arr, st, 0, n-1);
    return 0;
}

C - Tree Restoring

트리 지름의 특성을 활용합니다. 주어진 배열의 최대값이 지름 길이 d입니다. d의 홀짝성에 따라 중앙값의 개수 조건이 달라지며, 나머지 값들은 특정 범위 내에 분포해야 합니다.

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

int main() {
    int n;
    cin >> n;
    vector<int> cnt(n+1, 0);
    int max_val = 0;
    
    for (int i=0; i<n; i++) {
        int a;
        cin >> a;
        cnt[a]++;
        max_val = max(max_val, a);
    }
    
    if (max_val == 1) {
        cout << (n == 1 ? "Possible" : "Impossible");
        return 0;
    }
    
    int mid = (max_val+1)/2;
    for (int i=mid; i<=max_val; i++) {
        int req = (i == mid && max_val%2==0) ? 1 : 2;
        if (cnt[i] < req) {
            cout << "Impossible";
            return 0;
        }
    }
    
    for (int i=1; i<mid; i++) {
        if (cnt[i]) {
            cout << "Impossible";
            return 0;
        }
    }
    
    cout << "Possible";
    return 0;
}

D - ~K Perm Counting

포함배제 원리와 조합론을 적용합니다. 금지된 위치를 체인으로 모델링한 후, 각 체인에서 DP를 수행하여 충돌 없는 배치 수를 계산합니다. 최종 결과는 (-1)^i * f_i * (N-i)!의 합입니다.

#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
const ll MOD = 924844033;

int main() {
    int n, k;
    cin >> n >> k;
    vector<ll> fact(n+1, 1), inv(n+1, 1);
    
    for (int i=1; i<=n; i++) {
        fact[i] = fact[i-1] * i % MOD;
        inv[i] = i==1 ? 1 : MOD - (MOD/i)*inv[MOD%i]%MOD;
    }
    
    vector<ll> dp(n+1, 0);
    dp[0] = 1;
    int chain_cnt = 0;
    
    for (int i=1; i<=k; i++) {
        for (int j=0; j<2; j++) {
            if (i+j*k > n) break;
            chain_cnt++;
            for (int s=n; s>=1; s--) {
                dp[s] = (dp[s] + dp[s-1]) % MOD;
            }
        }
    }
    
    ll ans = 0;
    for (int i=0; i<=n; i++) {
        ll term = fact[n-i] * dp[i] % MOD;
        if (i % 2) ans = (ans - term + MOD) % MOD;
        else ans = (ans + term) % MOD;
    }
    
    cout << ans;
    return 0;
}

E - Sugigma: The Showdown

두 트리 간 거리 비교를 통한 게임 이론 문제입니다. 빨간 트리에서 파란 트리로의 거리 비율을 분석해 도달 가능한 최대 거리를 계산합니다. 안전한 정점이 존재하면 무한히 게임을 지속할 수 있습니다.

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

int main() {
    int n, x, y;
    cin >> n >> x >> y;
    vector<vector<int>> red(n+1), blue(n+1);
    
    for (int i=1; i<n; i++) {
        int u, v;
        cin >> u >> v;
        red[u].push_back(v);
        red[v].push_back(u);
    }
    
    for (int i=1; i<n; i++) {
        int u, v;
        cin >> u >> v;
        blue[u].push_back(v);
        blue[v].push_back(u);
    }
    
    vector<int> blue_dist(n+1, -1);
    queue<int> q;
    q.push(y);
    blue_dist[y] = 0;
    
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int v : blue[u]) {
            if (blue_dist[v] == -1) {
                blue_dist[v] = blue_dist[u] + 1;
                q.push(v);
            }
        }
    }
    
    int max_dist = 0;
    vector<int> red_dist(n+1, -1);
    q.push(x);
    red_dist[x] = 0;
    
    while (!q.empty()) {
        int u = q.front(); q.pop();
        if (red_dist[u] < blue_dist[u]) {
            max_dist = max(max_dist, blue_dist[u]);
            for (int v : red[u]) {
                if (red_dist[v] == -1) {
                    red_dist[v] = red_dist[u] + 1;
                    q.push(v);
                }
            }
        }
    }
    
    cout << (max_dist >= n ? -1 : 2*max_dist);
    return 0;
}

F - Many Easy Problems

정점별 기여도를 조합 공식으로 변환합니다. NTT를 이용해 다항식 곱셈을 최적화합니다. 각 크기 y에 대한 답은 N * C(N,y) - Σ(a_x * C(x,y)) 형태로 표현됩니다.

#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
const ll MOD = 924844033, root = 5;

void ntt(vector<ll>& a, bool inv) {
    int n = a.size();
    for (int i=1, j=0; i<n; i++) {
        int bit = n/2;
        while (j & bit) j ^= bit, bit /= 2;
        j ^= bit;
        if (i < j) swap(a[i], a[j]);
    }
    
    for (int len=2; len<=n; len*=2) {
        ll wlen = 1, angle = (MOD-1)/len;
        if (inv) angle = MOD-1-angle;
        wlen = pow(root, angle);
        
        for (int i=0; i<n; i+=len) {
            ll w = 1;
            for (int j=0; j<len/2; j++) {
                ll u = a[i+j], v = a[i+j+len/2]*w % MOD;
                a[i+j] = (u+v) % MOD;
                a[i+j+len/2] = (u-v+MOD) % MOD;
                w = w*wlen % MOD;
            }
        }
    }
    
    if (inv) {
        ll inv_n = pow(n, MOD-2);
        for (ll& x : a) x = x*inv_n % MOD;
    }
}

int main() {
    int n;
    cin >> n;
    vector<vector<int>> graph(n+1);
    vector<ll> fact(n+1,1), inv_fact(n+1,1);
    
    for (int i=1; i<=n; i++) {
        fact[i] = fact[i-1]*i % MOD;
        inv_fact[i] = pow(fact[i], MOD-2);
    }
    
    for (int i=1; i<n; i++) {
        int u, v;
        cin >> u >> v;
        graph[u].push_back(v);
        graph[v].push_back(u);
    }
    
    vector<ll> poly(n+1,0);
    function<int(int,int)> dfs = [&](int u, int p) {
        int sz = 1;
        for (int v : graph[u]) {
            if (v == p) continue;
            int ch_sz = dfs(v, u);
            poly[ch_sz]++;
            poly[n-ch_sz]++;
            sz += ch_sz;
        }
        return sz;
    };
    
    dfs(1,0);
    poly[0] = 0;
    
    vector<ll> A(n+1), B(n+1);
    for (int i=0; i<=n; i++) {
        A[i] = poly[i] * fact[i] % MOD;
        B[i] = inv_fact[n-i];
    }
    
    ntt(A, false);
    ntt(B, false);
    for (int i=0; i<A.size(); i++) A[i] = A[i]*B[i] % MOD;
    ntt(A, true);
    
    for (int k=1; k<=n; k++) {
        ll term1 = n * fact[n] % MOD * inv_fact[k] % MOD * inv_fact[n-k] % MOD;
        ll term2 = A[n+k] * inv_fact[k] % MOD;
        cout << (term1 - term2 + MOD) % MOD << '\n';
    }
    return 0;
}

태그: 스택 세그먼트 트리 트리 다이나믹 프로그래밍 그래프 이론

5월 31일 02:30에 게시됨