题目大意
定义无平方因子数为不能被任意一个质数的平方整除的数。给出
T
T
T个询问,每个询问给出一个
N
N
N和
K
K
K,问从
[
1
,
N
]
[1,N]
[1,N]中不重复地选出不超过
K
K
K个整数,且选出的数的乘积是无平方因子数的情况数。
对于
10
%
10\%
10%的数据,
N
≤
8
N \leq 8
N≤8;
对于
40
%
40\%
40%的数据,
N
≤
16
N \leq 16
N≤16;
对于
70
%
70\%
70%的数据,
N
≤
30
N \leq 30
N≤30;
对于
100
%
100\%
100%的数据,
1
≤
T
≤
5
1 \leq T \leq 5
1≤T≤5,
1
≤
K
≤
N
≤
500
1 \leq K \leq N \leq 500
1≤K≤N≤500。
分析
这是一道
D
P
DP
DP题。我们考虑分别处理每个询问。由“无平方因子数为不能被任意一个质数的平方整除的数”可知:选出的数的乘积分解质因数后每个质数的指数最高为
1
1
1。于是我们可以用二进制状态表示每个质数的出现情况。但是当我们尝试设
f
i
,
j
,
s
f_{i,j,s}
fi,j,s表示在
[
1
,
i
]
[1,i]
[1,i]中不重复地选出
j
j
j个整数,且每个质数的出现情况为二进制状态集合
s
s
s时,选出的数的乘积是无平方因子数的情况数的时候,我们发现由于
[
1
,
500
]
[1,500]
[1,500]中有
95
95
95个质数,
s
s
s的范围(
0
0
0到
2
95
−
1
2^{95}-1
295−1)会使数组大小超出空间限制。
于是我们考虑对这
95
95
95个质数分类。我们把它们分成两类:小于
23
23
23的(有
2
,
3
,
5
,
7
,
11
,
13
,
17
,
19
2,3,5,7,11,13,17,19
2,3,5,7,11,13,17,19,记为
p
1
p_1
p1),不小于
23
23
23的(有
23
,
29
,
31
,
⋯
,
499
23,29,31,\cdots,499
23,29,31,⋯,499,共有
87
87
87个,记为
p
2
p_2
p2)。因为
2
3
2
=
529
>
500
23^2=529>500
232=529>500,所以
[
1
,
n
]
[1,n]
[1,n]中每个整数的质因子中最多只有一个
p
2
p_2
p2中的质数。然后我们对
[
1
,
n
]
[1,n]
[1,n]中的每个整数分类:我们把是
p
2
p_2
p2中某个数的正整数倍的数划分到一类,再把不是
p
2
p_2
p2中任何数的正整数倍的数划分到另外一类中。
我们先处理只选最后一类数时的结果,此时这些数只会是
p
1
p_1
p1中的数的正整数倍。因为
p
1
p_1
p1中的质数只有
8
8
8个,所以我们可以直接设
f
i
,
j
,
s
f_{i,j,s}
fi,j,s表示在最后一类数与
[
1
,
i
]
[1,i]
[1,i]的交集中不重复地选出
j
j
j个整数,且
p
1
p_1
p1中每个质数的出现情况为二进制状态集合
s
s
s时,选出的数的乘积是无平方因子数的情况数。转移很容易得到:
f
i
,
j
,
s
=
∑
a
=
1
i
−
1
(
f
a
,
j
−
1
,
s
⊗
t
u
r
n
(
a
)
×
[
t
u
r
n
(
a
)
∈
s
]
)
f_{i,j,s}= \sum_{a=1}^{i-1}(f_{a,j-1,s \otimes turn(a)} \times[turn(a) \in s])
fi,j,s=∑a=1i−1(fa,j−1,s⊗turn(a)×[turn(a)∈s]),其中
t
u
r
n
(
a
)
turn(a)
turn(a)表示
p
1
p_1
p1中每个质数在
a
a
a中的出现情况的状态集合(下同)。初始时
f
1
,
0
,
0
=
f
1
,
1
,
0
=
1
f_{1,0,0}=f_{1,1,0}=1
f1,0,0=f1,1,0=1,分别表示选和不选
1
1
1的情况。
其实在计算
f
f
f时,有一个小优化。因为由无平方因子数的定义可知:只有无平方因子数相乘才能得到无平方因子数,所以我们可以先算出中最后一类数中的无平方因子数(最多有
73
73
73个,记为
a
a
a)和
p
1
p_1
p1中每个质数在
a
a
a中每一个数的出现情况
m
a
s
k
mask
mask,然后直接枚举
a
a
a中的每一个数转移即可。同时,还可以把
f
f
f的定义改为“在
a
1
,
2
,
⋯
,
i
a_{1,2, \cdots ,i}
a1,2,⋯,i中不重复地选出
j
j
j个整数,且
p
1
p_1
p1中每个质数的出现情况为二进制状态集合
s
s
s时,选出的数的乘积是无平方因子数的情况数”以压缩空间。
处理完只选不是
p
2
p_2
p2中任何数的正整数倍的数,我们要处理剩下的部分了。与
f
f
f类似,设
g
i
,
j
,
s
g_{i,j,s}
gi,j,s表示在最后一类数与前
i
i
i类数的并集中不重复地选出
j
j
j个整数,且
p
1
p_1
p1中每个质数的出现情况为二进制状态集合
s
s
s时,选出的数的乘积是无平方因子数的情况数。转移也很容易得到:
g
i
,
j
,
s
=
∑
a
∈
S
i
,
t
u
r
n
(
a
)
∈
s
g
i
−
1
,
j
−
1
,
s
⊗
t
u
r
n
(
a
)
g_{i,j,s}= \sum_{a \in S_i,turn(a) \in s} g_{i-1,j-1,s \otimes turn(a)}
gi,j,s=∑a∈Si,turn(a)∈sgi−1,j−1,s⊗turn(a),其中
S
i
S_i
Si表示第
i
i
i类数(下同)。初始时
g
1
,
i
,
j
=
∑
k
∈
S
1
,
t
u
r
n
(
k
)
∈
j
f
l
a
s
t
,
i
−
1
,
j
⊗
t
u
r
n
(
k
)
g_{1,i,j}= \sum_{k \in S_1,turn(k) \in j} f_{last,i-1,j \otimes turn(k)}
g1,i,j=∑k∈S1,turn(k)∈jflast,i−1,j⊗turn(k),其中
a
l
a
s
t
≤
n
a_{last} \leq n
alast≤n,同时
l
a
s
t
=
73
last=73
last=73或
a
l
a
s
t
+
1
>
n
a_{last+1}>n
alast+1>n。类似于优化计算
f
f
f,因为只有无平方因子数相乘才能得到无平方因子数,所以我们枚举
k
k
k时可以改为枚举
a
a
a中的每一个不大于
n
当
前
集
合
对
应
的
质
数
\frac{n}{当前集合对应的质数}
当前集合对应的质数n的数,并利用它对应的
m
a
s
k
mask
mask值转移。
易得答案为
∑
i
=
1
K
∑
j
=
0
2
8
−
1
g
87
,
i
,
j
\sum_{i=1}^{K} \sum_{j=0}^{2^8-1} g_{\space 87,i,j}
∑i=1K∑j=028−1g 87,i,j。至此,这道题目被以
O
(
T
×
(
73
×
m
×
2
8
+
87
×
73
×
m
×
2
8
)
)
O(T \times (73 \times m \times 2^8 + 87 \times 73 \times m \times 2^8))
O(T×(73×m×28+87×73×m×28))的时间复杂度解决。
代码
根据思路,可以写出如下代码:
#include<cstdio>
constexpr int mod = 1000000007/*定义模数*/,
a[] = { 1,2,3,5,6,7,10,11,13,14,15,17,19,21,22,26,30,33,34,35,38,39,42,51,55,57,65,66,70,77,78,85,91,95,102,105,110,114,119,130,133,143,154,165,170,182,187,190,195,209,210,221,231,238,247,255,266,273,285,286,323,330,357,374,385,390,399,418,429,442,455,462,494 },
mask[] = { 0,1,2,4,3,8,5,16,32,9,6,64,128,10,17,33,7,18,65,12,129,34,11,66,20,130,36,19,13,24,35,68,40,132,67,14,21,131,72,37,136,48,25,22,69,41,80,133,38,144,15,96,26,73,160,70,137,42,134,49,192,23,74,81,28,39,138,145,50,97,44,27,161 },
p2[] = { 23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499 };
int f[505][256]/*DP的数组,第一维被压缩掉了,对应分析中的f和g*/, temp[505][256]/*辅助数组,计算g时用*/;
int main()
{
static_cast<void>(freopen("mul.in", "r", stdin)); //定义文件输入输出
static_cast<void>(freopen("mul.out", "w", stdout));
int T;
static_cast<void>(scanf("%d", &T)); //读入T
while (T--)
{
int n, K;
static_cast<void>(scanf("%d%d", &n, &K)); //读入n和K
f[0][0] = f[1][0] = 1; //初始化f,此时程序中的f对应分析中的f
int cnt = 2; //减少DP循环范围用
for (int i = 1; i < 73 && a[i] <= n; ++i) //枚举a中的每一个数
{
const int& s = mask[i];
for (int j = cnt; j >= 1; --j) //计算f
{
const int jM1 = j - 1;
for (int k = 0; k < 256; ++k)
{
if (f[jM1][k] != 0 && (k & s) == 0)
{
int& upd = f[j][k | s];
upd += f[jM1][k];
if (upd >= mod)
{
upd -= mod;
}
}
}
}
if (cnt < K) //更新cnt
{
++cnt;
}
}
for (const auto& i : p2) //通过枚举P2中的质数枚举前78类数,此时程序中的f对应分析中的g
{
const int t = n / i;
for (int j = 0; j < 73 && a[j] <= t; ++j) //枚举当前类中的每一个数
{
const int& s = mask[j];
for (int k = 1; k <= cnt; ++k) //计算g
{
const int kM1 = k - 1;
for (int u = 0; u < 256; ++u)
{
if (f[kM1][u] != 0 && (u & s) == 0)
{
int& upd = temp[k][u | s];
upd += f[kM1][u];
if (upd >= mod)
{
upd -= mod;
}
}
}
}
}
for (int j = 1; j <= cnt; ++j) //更新g并清零temp
{
for (int k = 0; k < 256; ++k)
{
f[j][k] += temp[j][k];
if (f[j][k] >= mod)
{
f[j][k] -= mod;
}
temp[j][k] = 0;
}
}
if (cnt < K) //更新cnt
{
++cnt;
}
}
int ans = 0;
for (int i = 1; i <= cnt; ++i) //统计答案并清零数组
{
for (int j = 0; j < 256; ++j)
{
ans += f[i][j];
if (ans >= mod)
{
ans -= mod;
}
f[i][j] = 0;
}
}
printf("%d\n", ans); //输出答案
}
return 0;
}
总结
这道题在考察 D P DP DP和状态压缩的同时,还考察了分块、分组的思想,需要灵活的思维才能做对。