写在前面的话:嘤,我终于把我上次比赛的题补完了
题目:D. Counting Factorizations
题目大意:给你一个集合含有
2
∗
n
2*n
2∗n个数,问集合使用唯一分解定理能表示出多少个数?其中底数必须满足:
p
1
<
p
2
<
p
3
<
.
.
.
<
p
n
p_{1}<p_{2}<p{3}<...<p_{n}
p1<p2<p3<...<pn
解题思路:
其实就是从多于
n
n
n个质数当中选出
n
n
n个数来,然后剩下的都作为质数,每一种方案当中质数是固定的,对指数使用排列组合,也就是
n
!
c
n
t
1
!
∗
c
n
t
2
!
.
.
.
\frac{n!}{cnt_{1}!*cnt_{2}!...}
cnt1!∗cnt2!...n!
首先需要把这些方案分成正交的。
我们知道组合数当中有一个经典的方程为
c
(
i
,
j
)
=
c
(
i
−
1
,
j
−
1
)
+
c
(
i
−
1
,
j
)
c(i,j)=c(i-1,j-1)+c(i-1,j)
c(i,j)=c(i−1,j−1)+c(i−1,j)
其中
c
(
i
,
j
)
c(i,j)
c(i,j)表示的含义是从前
i
i
i个数中选出
j
j
j个符合条件的数的方案。
那么这个题,我们也可以这么来设。设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示从前 i i i个数(这些数应该去重)当中选出 j j j个质数的方案数。那么当由 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i−1][j−1]状态转移过来的时候就代表第 i i i个数(前提是质数)应该是底数,则第 i i i个数对于指数的贡献就是 c n t [ i ] − 1 cnt[i]-1 cnt[i]−1,也就是 d p [ i − 1 ] [ j − 1 ] ( c n t [ i ] − 1 ] ) ! \frac{dp[i-1][j-1]}{(cnt[i]-1])!} (cnt[i]−1])!dp[i−1][j−1];如果是由 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]转移过来的时候就代表不选择第 i i i个数作为底数。则其对指数的贡献为 d p [ i − 1 ] [ j ] ( c n t [ i ] ] ) ! \frac{dp[i-1][j]}{(cnt[i]])!} (cnt[i]])!dp[i−1][j]
所以转移方程为:
for (int i = 1; i < kl.size(); i++)
{
dp[i][0] = div1(dp[i - 1][0] , fact[cnt[kl[i]]]);
for (int j = 1; j <= min(i, n); j++)
{
if (prime[kl[i]])
dp[i][j] = ((ll)div1(dp[i - 1][j - 1] , fact[cnt[kl[i]]-1]) +
div1(dp[i - 1][j] , fact[cnt[kl[i]]]))%mod;
else
dp[i][j] = div1(dp[i - 1][j] , fact[cnt[kl[i]]]);
}
}
这里需要注意每一个 d p [ i ] [ j ] dp[i][j] dp[i][j]实际上是你列的式子中的一部分,比如说,你的一种方案是 n ! c n t 1 ! ∗ c n t 2 ! . . . c n t n ! \frac{n!}{cnt_{1}!*cnt_{2}!...cnt_{n}! } cnt1!∗cnt2!...cntn!n!,那么 d p [ i ] [ j ] dp[i][j] dp[i][j]实际只是 1 c n t 1 ! ∗ c n t 2 ! . . . c n t j ! \frac{1}{cnt_{1}!*cnt_{2}!...cnt_{j}! } cnt1!∗cnt2!...cntj!1(最后再乘以 n ! n! n!), d p [ i ] [ j ] ( j < n ) dp[i][j](j<n) dp[i][j](j<n)的目的只是转移到下一个状态.
不超时体会:把所有的判别函数都存进数组。
代码:
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
const int length = 6000;
int reco[length];
const int len = 1e6 + 5;
vector<int> kl;
int cnt[len];
int ksm(int x, int t, int p)
{
int ans = 1;
while (t)
{
if (t % 2 == 1)
{
ans = 1ll * ans*x%p;
}
t = t >> 1;
x = 1ll * x*x%p;
}
return ans;
}
int chk[4] = { 2,3,5,7 };
int check(int x)
{
if (x < 10)
{
for (int i = 0; i < 4; i++)
{
if (x == chk[i])
{
return 1;
}
}
return 0;
}
int p = x;
int k = 0;
x--;
while (x%2==0)
{
x = x >> 1;
k++;
}
for (int i = 0; i < 4; i++)
{
int a = ksm(chk[i], x, p);
for (int j = 0; j < k; j++)
{
int tmp = 1ll * a*a%p;
if (tmp == 1 && a != 1 && a!=p - 1)
return 0;
a = tmp;
}
if (a != 1)
return 0;
}
return 1;
}
int fact[length];
int dp[length][length];
map<int,int> rev;
int mod = 998244353;
int rev1(int x)
{
return ksm(x, mod - 2, mod);
}
int div1(int a, int b)
{
return 1ll * a*rev[b] % mod;
}
int prime[len];
int main(void)
{
int n;
scanf_s("%d", &n);
int max_reco = -1;
for (int i = 0; i < 2*n; i++)
{
scanf_s("%d", &reco[i]);
cnt[reco[i]]++;
max_reco = max(max_reco, reco[i]);
}
fact[0] = 1;
for (int i = 1; i <= 2 * n; i++)
{
fact[i] = 1ll * fact[i - 1] * i%mod;
}
dp[0][0] = 1;
//kl.insert(kl.begin(), 0);
kl.push_back(0);
for (int i = 1; i <= max_reco; i++)
{
if (cnt[i])
{
kl.push_back(i);
rev[fact[cnt[i]]] = rev1(fact[cnt[i]]);
rev[fact[cnt[i] - 1]] = rev1(fact[cnt[i]-1]);
prime[i] = check(i);
}
}
for (int i = 1; i < kl.size(); i++)
{
dp[i][0] = div1(dp[i - 1][0] , fact[cnt[kl[i]]]);
for (int j = 1; j <= min(i, n); j++)
{
if (prime[kl[i]])
dp[i][j] = ((ll)div1(dp[i - 1][j - 1] , fact[cnt[kl[i]]-1]) +
div1(dp[i - 1][j] , fact[cnt[kl[i]]]))%mod;
else
dp[i][j] = div1(dp[i - 1][j] , fact[cnt[kl[i]]]);
}
}
int ans = 1ll * dp[kl.size() - 1][n] * fact[n]%mod;
printf("%d", ans);
}