为了更好的阅读体检,可以查看我的算法学习网
本题在线评测链接:P1126
题目内容
塔子哥是一个喜欢数学和游戏的年轻人。有一天,他发明了一个新的游戏,可以考验人们的智力和数学知识。
这个游戏的规则很简单:给定一个长度为 n n n的整数序列 a a a和一个正整数 k k k,玩家需要从序列 a a a中删掉一些数,使得剩下的数的最大公约数等于 k k k。例如,如果序列 a a a为 [ 2 , 4 , 6 , 8 ] [2,4,6,8] [2,4,6,8], k = 2 k=2 k=2,则玩家可以删掉 4 4 4和 8 8 8,剩下的数为 [ 2 , 6 ] [2,6] [2,6],它们的最大公约数是 2 2 2。
塔子哥希望这个游戏能够受到更多人的欢迎,因此他决定把这个游戏发布在他的网站上,并邀请人们来挑战。他相信,这个游戏不仅可以锻炼人们的数学能力,还能帮助人们更好地理解最大公约数的概念。
现在塔子哥想你问有多少种删除的方案。如果答案太大,就把它对 1 0 9 + 7 10^9+7 109+7 取模。
最大公约数:指两个或多个整数公有约数中最大的一个。
输入描述
第一行输入两个正整数 n , k n,k n,k
第二行输入 n n n 个正整数代表 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an
1 ⩽ n , a i ⩽ 1 0 5 1 \leqslant n, a_i \leqslant 10^5 1⩽n,ai⩽105
输出描述
输出一行整数,代表删除的方案数在 1 0 9 + 7 10^9 + 7 109+7 意义下的取值。
样例
输入
5 5
1 5 2 3 5
输出
3
思路1:数论+动态规划
1.切入点:一个很容易感觉到的,对我们很有启发性的事情
找出所有是 k k k的倍数的数,假设总共 x x x个。那么算下他们的子集个数 2 x − 1 2^{x} - 1 2x−1
这就是答案了?并不是! 这不是" g c d gcd gcd恰好等于 k k k" 的子集个数,而是" g c d gcd gcd是 k k k的倍数"的子集个数。观察他们的关系,我们可以发现:
" g c d gcd gcd是 k k k的倍数"的子集个数 = " g c d gcd gcd恰好是k"的子集个数 + " g c d gcd gcd恰好是 2 k 2k 2k"的子集个数 + … + " g c d gcd gcd恰好是 c k ck ck"的子集个数
那么可以得到:
“ g c d gcd gcd恰好是k"的子集个数” = " g c d gcd gcd是 k k k的倍数"的子集个数 - " g c d gcd gcd恰好是 2 k 2k 2k"的子集个数 - … - " g c d gcd gcd恰好是 c k ck ck"的子集个数
这个东西显然就很dp了。
2.动态规划呼之欲出
状态:
令 b k i bk_i bki 代表 序列中 i i i的个数 . 桶装即可求解
令 f i f_i fi 代表 序列中 是 i i i的倍数的数的个数
令 g i g_i gi 代表 序列中 g c d gcd gcd是 i i i的倍数 的子集个数
令 d p i dp_i dpi 代表 序列中 g c d gcd gcd恰好是 i i i 的子集个数
转移:
b
k
i
bk_i
bki :桶装一下就好
f
i
=
∑
i
∣
j
b
k
j
f_i = \sum_{i|j} bk_j
fi=i∣j∑bkj
f
i
f_i
fi 转移如上 , 类似埃式筛地去求即可。复杂度为
O
(
n
l
o
g
n
)
O(nlog\ n)
O(nlog n)
g
i
=
2
f
i
−
1
g_i = 2^{f_i} - 1
gi=2fi−1
g
i
g_i
gi 也直接求就好,但由于
f
i
f_i
fi 可能比较大,实际求的时候我们需要来个快速幂或者提前预处理一下
2
i
2^{i}
2i
d
p
i
=
g
i
−
∑
j
>
i
∧
i
∣
j
d
p
j
dp_i = g_i - \sum_{j>i \wedge i|j} dp_j
dpi=gi−j>i∧i∣j∑dpj
d
p
i
dp_i
dpi 转移如上 , 类似埃式筛地去求即可。复杂度为
O
(
n
l
o
g
n
)
O(nlog\ n)
O(nlog n) 。不过注意外层需要倒着扫。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
ll ksm (ll a , ll b){
ll ans = 1 , base = a;
while (b){
if (b & 1) ans = ans * base % mod;
base = base * base % mod;
b >>= 1;
}
return ans;
}
// 四个数组对应上面得四个状态
ll bk[maxn] , f[maxn] , g[maxn] , dp[maxn];
int main (){
int n , k;
cin >> n >> k;
for (int i = 1 ; i <= n ; i++){
int x; cin >> x;
bk[x] ++;
}
// 参考上述f的转移方程
for (int i = 1 ; i < maxn ; i++){
for (int j = i ; j < maxn ; j += i){
f[i] += bk[j];
}
}
// 参考上述g的转移方程
for (int i = 1 ; i < maxn ; i++){
g[i] = (ksm(2 , f[i]) - 1 + mod) % mod;
}
// 参考上述dp的转移方程
for (int i = maxn - 1 ; i >= 1 ; i --){
dp[i] = g[i];
for (int j = i + i ; j < maxn ; j += i){
dp[i] = (dp[i] - dp[j] + mod) % mod;
}
}
cout << dp[k] <<endl;
return 0;
}