D. Counting Factorizations

D. Counting Factorizations

The prime factorization of a positive integer m is the unique way to write it as m=p1e1p2e2pkek, where p1,p2,,pk are prime numbers, p1<p2<<pk and e1,e2,,ek 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)={p1,e1,p2,e2,,pk,ek}.

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

You are given a list consisting of 2n integers a1,a2,,a2n. Count how many positive integers m satisfy that f(m)={a1,a2,,a2n}. Since this value may be large, print it modulo 998244353.

Input

The first line contains one integer n (1n2022).

The second line contains 2n integers a1,a2,,a2n (1ai106)  — the given list.

Output

Print one integer, the number of positive integers m satisfying f(m)={a1,a2,,a2n} modulo 998244353.

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=2331 and 54=2133.

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 14 nor 41 are prime factorizations because 1 and 4 are not primes.

 

解题思路

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

  定义 bx 表示合数 x 的出现次数,假设有 s 个不同的合数。cx 表示质数 x 的出现,假设有 t 个不同的质数。当从 t 个不同质数中选择了 n 个作为底数,用 cx 表示每个质数剩余的个数,其中如果选择了 x,则 cx=cx1 否则 cx=cx。当确定了底数的选择后,那么合法的 m 的数量就取决于剩余的 n 个数能组成不同的指数方案,即n!b1!b2!bs!c1!c2!ct!

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

  注意到每个对于求和的每一项中,n!b1!b2!bs! 都是一样的,因此只需求每一项的 1c1!c2!ct! 的和,最后乘上 n!b1!b2!bs! 即可。

  由于在选择底数的方案中,第 i 个质数可选可不选,因此可以 01 背包来求所有方案的 1c1!c2!ct! 的总和。定义 f(i,j) 表示从前 i 个质数中选出了 j 个的所有方案对应项的总和。假设第 i 个质数是 x,状态转移方程为f(i,j)=f(i1,j)×1cx!+f(i1,j1)×1(cx1)!

  那么所有可能的底数选择方案对应的 1c1!c2!ct! 的总和就是 f(t,n),最终答案就是 n!b1!b2!bs!×f(t,n)

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

  AC 代码如下,时间复杂度为 O(nlog(mod)+n2)

#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

posted @   onlyblues  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2022-11-13 摆放棋子
2022-11-13 D. Yet Another Problem
Web Analytics
点击右上角即可分享
微信分享提示