본문 바로가기

알고리즘/백준 문제 풀이

[C++] 28866번 Морти покупает продукты

반응형

https://www.acmicpc.net/problem/28866


 

26/02/18

 

 

자주 나왔던 단골 유형의 FFT문제이다.


 

문제 접근 방식:

 

 

$N$개의 수들 중 중복을 포함하여 $K$개를 선택했을 때의 가능한 경우의 수를 세는 문제라고 요약할 수 있다.

이전에 너무 많이 했던 FFT문제 유형이다. 특히, 보석 가게 문제와 거의 유사하다고 볼 수 있다.

실제로 필요한 다항식의 차수는 $50\ 000$까지 밖에 안되므로, 거듭제곱을 하며 중간 중간 크기를 resize해줘도 상관 없다.

또한, 여기서 $786\ 433$이라는 특이한 수가 쓰이는데, 이 수는 $3\times 2^{18}+1$로 NTT-friendly한 소수이다. 원시근은 $10$을 사용하면 된다.

이 점만 유의하여 구현하고, 나온 결과를 누적합을 한 뒤 각 쿼리를 처리하면 충분하다.


아래는 내가 위의 접근 방식과 같이 작성한 C++ 코드이다. 더보기를 누르면 확인할 수 있다.

더보기
// 28866번 Морти покупает продукты
// NTT
#include <iostream>
#include <vector>

using namespace std;
#define fastio ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define endl '\n'

/*
MOD = 786'433 = 3*2^18 + 1 / G = 10 / Works for length up to 2^18 = 262'144
*/
template<int MOD, int G>
struct NTT {
private :
    // HELPER FUNCTION
    inline int mul_mod(int a, int b) const {return (long long)a * b % MOD;}
    inline int pow_mod(int a, int e) const {
        int r = 1;
        while(e > 0){
            if(e & 1) r = mul_mod(r, a);
            a = mul_mod(a, a);
            e >>= 1;
        }
        return r;
    }
    inline int mod_inv(int a) const {return pow_mod(a, MOD-2);}
    // ---- precompute cache (per template instantiation) ----
    static inline vector<int> rev;
    static inline vector<int> roots_fwd, roots_inv;
    static inline int prepared_n = 0; // current prepared size (power of two)
    void precompute(int n) {
        if (prepared_n == n) return;
        prepared_n = n;
        rev.assign(n, 0);
        int lg = 0;
        while ((1 << lg) < n) ++lg;
        for (int i = 0; i < n; i++) {
            rev[i] = 0;
            for (int b = 0; b < lg; b++){
                if (i & (1 << b)) rev[i] |= 1 << (lg - 1 - b);
            }
        }
        roots_fwd.assign(n, 1); roots_inv.assign(n, 1);
        for (int len = 1; len < n; len <<= 1) {
            int wlen_fwd = pow_mod(G, (MOD - 1) / (2 * len));
            int wlen_inv = mod_inv(wlen_fwd);
            int w_f = 1; int w_i = 1;
            for (int j = 0; j < len; j++) {
                roots_fwd[len + j] = w_f;
                roots_inv[len + j] = w_i;
                w_f = mul_mod(w_f, wlen_fwd);
                w_i = mul_mod(w_i, wlen_inv);
            }
        }
    }
public : 
    void iterative_NTT(vector<int>& a, int invert){
        int n = (int)a.size();
        precompute(n);
        // 1) bit-reversal permutation (use rev[])
        for (int i = 1; i < n; i++){
            int j = rev[i];
            if (i < j) swap(a[i], a[j]);
        }
        // 2) butterflies by length = 2, 4, 8, ...
        // use roots_fwd/roots_inv precomputed
        const auto& roots = invert ? roots_inv : roots_fwd;
        for (int len = 2; len <= n; len <<= 1){
            int half = len >> 1;
            for (int i = 0; i < n; i += len){
                for (int j = 0; j < len / 2; j++){
                    int u = a[i+j];
                    int v = mul_mod(roots[half+j], a[i+j+half]);
                    int x = u+v; if (x >= MOD) x -= MOD;
                    int y = u-v; if (y < 0) y += MOD;
                    a[i+j] = x;
                    a[i+j+half] = y;
                }
            }
        }
        // 3) Divide by n (multiply by inverse) for inverse transform
        if (invert){
            int inv_n = mod_inv(n);
            for (int i = 0; i < n; ++i) a[i] = mul_mod(a[i], inv_n);
        }
    }
    // Input : Coefficient vector {a_0, a_1, ...}, {b_0, b_1, ...}
    // Output : Convolution of two coefficient vector
    vector<int> convolution(vector<int> a, vector<int> b) {
        if (a.empty() || b.empty()) return {};
        int S_a = (int)a.size(), S_b = (int)b.size();
        // Make vector size easy to dnc(2^n).
        int n = 1;
        while (n < S_a + S_b - 1){
            n <<= 1;
        }
        a.resize(n); b.resize(n);
        // Normalize to [0, MOD)
        for (int i = 0; i < n; ++i){
            a[i] %= MOD; if (a[i] < 0) a[i] += MOD;
            b[i] %= MOD; if (b[i] < 0) b[i] += MOD;
        }
        // NTT
        iterative_NTT(a, 0); iterative_NTT(b, 0);
        // Pointwise product
        for (int i = 0; i < n; i++) {
            a[i] = mul_mod(a[i], b[i]);
        }
        // INTT
        iterative_NTT(a, 1);
        a.resize(S_a + S_b - 1);
        return a;
    }
    // Input : Coefficient vector A = {a_0, a_1, ...} and Power
    // Output : Coefficient vector of A^P
    vector<int> power(vector<int> a, int e){
        if (a.empty()) return {};
        if (e == 0) return {1};
        int S_a = (int)a.size();
        // Make vector size easy to dnc(2^n).
        int n = 1;
        while (n < (S_a - 1)*e + 1){
            n <<= 1;
        }
        a.resize(n);
        // Normalize to [0, MOD)
        for (int i = 0; i < n; ++i){
            a[i] %= MOD; if (a[i] < 0) a[i] += MOD;
        }
        // NTT
        iterative_NTT(a, 0);
        // Pointwise product
        for (int i = 0; i < n; i++) {
            a[i] = pow_mod(a[i], e);
        }
        // INTT
        iterative_NTT(a, 1);
        a.resize((S_a - 1)*e + 1);
        return a;
    }
};

vector<int> poly_pow(vector<int> poly, int K){
    NTT<786'433, 10> ntt;
    vector<int> ret = {1};
    while (K > 0){
        if (K & 1){
            ret = ntt.convolution(ret, poly);
            ret.resize(50001);
        }
        poly = ntt.convolution(poly, poly);
        poly.resize(50001);
        K >>= 1;
    }
    return ret;
}

int main(void){
    fastio
    
    int N, K, Q; cin >> N >> K >> Q;
    vector<int> poly(50001, 0);
    int c;
    for (int i = 0; i < N; ++i){
        cin >> c;
        ++poly[c];
    }
    auto ret_poly = poly_pow(poly, K);
    for (int i = 1; i < 50001; ++i){
        ret_poly[i] += ret_poly[i-1];
        ret_poly[i] %= 786'433;
    }
    int l, r;
    for (int i = 0; i < Q; ++i){
        cin >> l >> r;
        int query_ans = ret_poly[r] - ret_poly[l-1];
        if (query_ans < 0) query_ans += 786'433;
        cout << query_ans << endl;
    }
    return 0;
}
반응형

'알고리즘 > 백준 문제 풀이' 카테고리의 다른 글

[C++] 4442번 빌보의 생일  (0) 2026.03.03
[C++] 9463번 순열 그래프  (0) 2026.03.03
[C++] 29021번 IPvK  (0) 2026.03.02
[C++] 20340번 Lost Map  (0) 2026.03.01
[C++] 30977번 금강산도 식후경  (0) 2026.03.01