【备战秋招】每日一题:2023.03.26-腾讯实习-第五题-序列最大公约数

为了更好的阅读体检,可以查看我的算法学习网
本题在线评测链接: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 1n,ai105

输出描述

输出一行整数,代表删除的方案数在 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 2x1

这就是答案了?并不是! 这不是" 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=ijbkj
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=2fi1
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=gij>iijdpj
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值