📜  素子集积问题(1)

📅  最后修改于: 2023-12-03 15:11:34.687000             🧑  作者: Mango

素子集积问题

概念

素子集积问题是指在一个集合中选择若干个数相乘,使得其积为素数的方案数。

解法
朴素算法

朴素算法就是枚举所有可能的子集,计算每个子集对应的积是否为素数。时间复杂度为 $2^n$。

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

bool is_prime(int n) {
    if (n <= 1)
        return false;
    for (int i = 2; i <= sqrt(n); ++i) {
        if (n % i == 0)
            return false;
    }
    return true;
}

int subset_product(vector<int>& nums) {
    int res = 0;
    int n = nums.size();
    for (int i = 1; i < (1 << n); ++i) {
        int p = 1;
        for (int j = 0; j < n; ++j) {
            if (i & (1 << j)) {
                p *= nums[j];
            }
        }
        if (is_prime(p)) {
            ++res;
        }
    }
    return res;
}

int main() {
    vector<int> nums{2, 3, 5, 7};
    int res = subset_product(nums);
    cout << res << endl; // 8
    return 0;
}
埃氏筛法

我们首先可以枚举出所有小于 $n$ 的质数 $p_1,p_2,p_3,\cdots,p_m$,然后对于每个 $p_i$,枚举出所有可能的子集,计算其积是否为 $p_i$。这样我们就能够得出所有积为素数的方案数。

但是,求出小于 $n$ 的质数本身就是一个比较费事的问题。所以我们可以先用埃氏筛法求出所有小于 $n$ 的素数,然后再进行枚举。时间复杂度为 $O(2^mp(m))$,其中 $p(m)$ 表示小于等于 $m$ 的素数个数。

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

int get_primes(int n, vector<int>& primes) {
    vector<bool> is_prime(n + 1, true);
    int cnt = 0;
    for (int i = 2; i <= n; ++i) {
        if (is_prime[i]) {
            primes.push_back(i);
            ++cnt;
        }
        for (auto p : primes) {
            if (i * p > n)
                break;
            is_prime[i * p] = false;
            if (i % p == 0)
                break;
        }
    }
    return cnt;
}

int subset_product(vector<int>& nums) {
    vector<int> primes;
    int cnt = get_primes(*max_element(nums.begin(), nums.end()), primes);
    int res = 0;
    int n = nums.size();
    for (int i = 0; i < cnt; ++i) {
        int p = primes[i];
        for (int j = 1; j < (1 << n); ++j) {
            int t = 1;
            for (int k = 0; k < n; ++k) {
                if (j & (1 << k)) {
                    t *= nums[k];
                }
            }
            if (t == p) {
                ++res;
            }
            if (t > p) {
                break;
            }
        }
    }
    return res;
}

int main() {
    vector<int> nums{2, 3, 5, 7};
    int res = subset_product(nums);
    cout << res << endl; // 8
    return 0;
}
容斥原理

容斥原理是一种比较常用的计数方法,对于一些比较难计算的问题可以起到简化的作用。对于素子集积问题,我们可以考虑容斥原理:对于每个素数 $p$,将所有积为 $p$ 的子集个数相加,减去所有积为 $p_1p_2$ 的子集个数,再加上所有积为 $p_1p_2p_3$ 的子集个数……以此类推。这样就可以得到积为素数的子集个数。

时间复杂度为 $O(3^np(n))$,其中 $p(n)$ 表示小于等于 $n$ 的素数个数。

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

int subset_product(vector<int>& nums) {
    int n = nums.size();
    int res = 0;
    for (int i = 1; i < (1 << n); ++i) {
        int p = 1, cnt = 0;
        for (int j = 0; j < n; ++j) {
            if (i & (1 << j)) {
                p *= nums[j];
                ++cnt;
            }
        }
        if (cnt % 2 == 1) {
            res += 1;
        } else {
            res -= 1;
        }
    }
    return res;
}

int main() {
    vector<int> nums{2, 3, 5, 7};
    int res = subset_product(nums);
    cout << res << endl; // 8
    return 0;
}
总结

对于素子集积问题,我们有多种解法,包括朴素算法、埃氏筛法和容斥原理。其中,实际应用中,基本不会使用朴素算法。埃氏筛法和容斥原理的时间复杂度都比较高,但对于规模不太大的数据都是可行的。需要根据具体情况灵活选择算法。