D. Counting Factorizations

发布时间 2023-11-13 20:06:26作者: onlyblues

D. Counting Factorizations

The prime factorization of a positive integer $m$ is the unique way to write it as $\displaystyle m=p_1^{e_1}\cdot p_2^{e_2}\cdot \ldots \cdot p_k^{e_k}$, where $p_1, p_2, \ldots, p_k$ are prime numbers, $p_1 < p_2 < \ldots < p_k$ and $e_1, e_2, \ldots, e_k$ are positive integers.

For each positive integer $m$, $f(m)$ is defined as the multiset of all numbers in its prime factorization, that is $f(m)=\{p_1,e_1,p_2,e_2,\ldots,p_k,e_k\}$.

For example, $f(24)=\{2,3,3,1\}$, $f(5)=\{1,5\}$ and $f(1)=\{\}$.

You are given a list consisting of $2n$ integers $a_1, a_2, \ldots, a_{2n}$. Count how many positive integers $m$ satisfy that $f(m)=\{a_1, a_2, \ldots, a_{2n}\}$. Since this value may be large, print it modulo $998\,244\,353$.

Input

The first line contains one integer $n$ ($1\le n \le 2022$).

The second line contains $2n$ integers $a_1, a_2, \ldots, a_{2n}$ ($1\le a_i\le 10^6$)  — the given list.

Output

Print one integer, the number of positive integers $m$ satisfying $f(m)=\{a_1, a_2, \ldots, a_{2n}\}$ modulo $998\,244\,353$.

Examples

input

2
1 3 2 3

output

2

input

2
2 2 3 5

output

5

input

1
1 4

output

0

Note

In the first sample, the two values of $m$ such that $f(m)=\{1,2,3,3\}$ are $m=24$ and $m=54$. Their prime factorizations are $24=2^3\cdot 3^1$ and $54=2^1\cdot 3^3$.

In the second sample, the five values of $m$ such that $f(m)=\{2,2,3,5\}$ are $200, 225, 288, 500$ and $972$.

In the third sample, there is no value of $m$ such that $f(m)=\{1,4\}$. Neither $1^4$ nor $4^1$ are prime factorizations because $1$ and $4$ are not primes.

 

解题思路

  对于合法的 $f(m)=\{a_1, a_2, \ldots, a_{2n}\}$,$a_1, a_3, \ldots, a_{2n-1}$ 相当于 $m$ 质因数分解后的 $n$ 项底数,要求是 $n$ 个互不相同且递增的质数。剩下的 $a_2, a_4, \ldots, a_{2n}$ 则是对应项的指数,可以是任意数。因此在初始的 $2n$ 个元素中,如果互不相同的质数的数量小于 $n$,那么就不存在合法方案,答案为 $0$。

  定义 $b_x$ 表示合数 $x$ 的出现次数,假设有 $s$ 个不同的合数。$c_x$ 表示质数 $x$ 的出现,假设有 $t$ 个不同的质数。当从 $t$ 个不同质数中选择了 $n$ 个作为底数,用 $c'_x$ 表示每个质数剩余的个数,其中如果选择了 $x$,则 $c'_x = c_x - 1$ 否则 $c'_x = c_x$。当确定了底数的选择后,那么合法的 $m$ 的数量就取决于剩余的 $n$ 个数能组成不同的指数方案,即$$\frac{n!}{b_1!\:\:b_2!\ldots b_s!\:\:c'_1!\:\:c'_2!\ldots c'_t!}$$

  考虑所有可能的底数选择方案,那么最终答案就是各个方案所对应的上式的和。

  注意到每个对于求和的每一项中,$\dfrac{n!}{b_1!\:\:b_2!\ldots b_s!}$ 都是一样的,因此只需求每一项的 $\dfrac{1}{c'_1!\:\:c'_2!\ldots c'_t!}$ 的和,最后乘上 $\dfrac{n!}{b_1!\:\:b_2!\ldots b_s!}$ 即可。

  由于在选择底数的方案中,第 $i$ 个质数可选可不选,因此可以 01 背包来求所有方案的 $\dfrac{1}{c'_1!\:\:c'_2!\ldots c'_t!}$ 的总和。定义 $f(i,j)$ 表示从前 $i$ 个质数中选出了 $j$ 个的所有方案对应项的总和。假设第 $i$ 个质数是 $x$,状态转移方程为$$f(i,j) = f(i-1,j) \times \frac{1}{c_x!} + f(i-1,j-1) \times \frac{1}{(c_x - 1)!}$$

  那么所有可能的底数选择方案对应的 $\dfrac{1}{c'_1!\:\:c'_2!\ldots c'_t!}$ 的总和就是 $f(t,n)$,最终答案就是 $\dfrac{n!}{b_1!\:\:b_2!\ldots b_s!} \times f(t,n)$。

  由于涉及到出除法,可以先通过 $O(n \log {(\text{mod})})$ 的复杂度预处理出所有阶乘的乘法逆元,在状态转移时只需乘上阶乘的乘法逆元即可。

  AC 代码如下,时间复杂度为 $O(n \log ({\text{mod}}) + n^2)$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 4100, M = 1e6 + 10, mod = 998244353;

int a[N], c[M];
int primes[M], cnt;
bool vis[M];
int infact[N];
int f[N];

void get_prime(int n) {
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) primes[cnt++] = i;
        for (int j = 0; primes[j] * i <= n; j++) {
            vis[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

int qmi(int a, int k) {
    int ret = 1;
    while (k) {
        if (k & 1) ret = 1ll * ret * a % mod;
        a = 1ll * a * a % mod;
        k >>= 1;
    }
    return ret;
}

int main() {
    int n;
    scanf("%d", &n);
    infact[0] = 1;
    for (int i = 1; i <= n << 1; i++) {
        scanf("%d", a + i);
        c[a[i]]++;    // 统计每个元素的出现次数
        infact[i] = 1ll * infact[i - 1] * qmi(i, mod - 2) % mod;    // 计算阶乘i!的逆元 
    }
    int ret = 1;
    for (int i = 1; i <= n; i++) {    // 计算n!
        ret = 1ll * ret * i % mod;
    }
    get_prime(M - 1);
    vis[1] = true;
    vector<int> p;
    for (int i = 1; i < M; i++) {
        if (c[i]) {
            if (vis[i]) ret = 1ll * ret * infact[c[i]] % mod;    // 合数则直接乘上阶乘的逆元
            else p.push_back(i);    // 把质数找出来
        }
    }
    f[0] = 1;
    for (auto &x : p) {
        for (int j = n; j >= 0; j--) {    // 01背包的优化写法
            f[j] = 1ll * f[j] * infact[c[x]] % mod;
            f[j] = (f[j] + 1ll * f[j - 1] * infact[c[x] - 1]) % mod;
        }
    }
    ret = 1ll * ret * f[n] % mod;    // 把质数部分的逆元乘上
    printf("%d", ret);
    
    return 0;
}

 

参考资料

  Codeforces Round 856 (Div. 2) Editorial:https://codeforces.com/blog/entry/113500