abcdeffa's Blog

当局者迷,旁观者清。

0%

GMOJ S5464 【乘积】

Description

给你两个正整数 $n$ 和 $k$,问你在 $1∼n$ 中选不超过 $k$ 个数,它们的积不含平方质因子的方案数。

Solution

由于在 $\sqrt{n}$ 以内的质数个数只有 9 个,因此考虑把当前乘积含 $\sqrt{n}$ 以内质因子的情况用一个二进制数 $S$ 来表示,S 从低位往高位数的第 $i$ 个二进制位为 0 / 1 表示当前的乘积不含有 / 含有一个第 $i$ 小的质数这一因子。

显然不可能含有两个及以上,否则它们的积就含平方质因子了。

对于含同一大于 $\sqrt{n}$ 的质因子的数,我们把它们归到同一类,因此显然在同一类中我们最多只能够取一个数。

于是我们想到设 $f_{i, j, S}$ 表示当前取完了质因子 $i$ 所在的这一列,已经选了 $j$ 个数,含 $\sqrt{n}$ 以内的质因子的情况为 $S$ 的方案数。

然后发现 $i$ 那一维可以滚动掉,然后随便转移一下即可。

注意 Code 中的 $j$ 那一维要倒着枚举,这个你想一下 01 背包和完全背包转移枚举顺序的区别就明白了。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <cstdio>
#include <cstring>
#define mod 1000000007
#define maxN 510
int f[maxN][1 << 8], g[maxN][maxN];
int state[maxN];
int prime[10] = {2, 3, 5, 7, 11, 13, 17, 19};
int main ()
{
for(int i = 1;i < maxN; i++)
{
int now = i;
for(int j = 0;j <= 7; j++)
{
if(!(now % (prime[j] * prime[j])))
{
state[i] = -1;
break;
}
if(!(now % prime[j]))
{
state[i] |= (1 << j);
now /= prime[j];
}
}
}
int T = 0;
scanf("%d", &T);
const int maxS = (1 << 8) - 1;
while(T--)
{
memset(f, 0, sizeof(f));
int n = 0, k = 0;
scanf("%d %d", &n, &k);
for(int i = 1;i <= n; i++)
{
if(state[i] < 0)
{
continue;
}
int now = i;
for(int j = 0;j <= 7; j++)
{
if(!(now % prime[j]))
{
now /= prime[j];
}
}
if(now > 1)
{
g[now][++g[now][0]] = i;
}
else
{
g[i][++g[i][0]] = i;
}
}
f[0][0] = 1;
for(int i = 1;i <= n; i++)
{
if(g[i][0])
{
for(int j = k - 1;j >= 0; j--)
{
for(int p = 1;p <= g[i][0]; p++)
{
int now = g[i][p];
for(int S = 0;S <= maxS; S++)
{
if(!(S & state[now]))
{
f[j + 1][S | state[now]] += f[j][S];
f[j + 1][S | state[now]] %= mod;
}
}
}
}
}
}
for(int i = 1;i <= n; i++)
{
g[i][0] = 0;
}
int Ans = 0;
for(int i = 1;i <= k; i++)
{
for(int S = 0;S <= maxS; S++)
{
Ans += f[i][S];
Ans %= mod;
}
}
printf("%d\n", Ans);
}
return 0;
}