GCD Expectation ZOJ - 3868 (容斥原理+快速幂)

GCD Expectation

ZOJ - 3868

Edward has a set of n integers {a1, a2,...,an}. He randomly picks a nonempty subset {x1, x2,…,xm} (each nonempty subset has equal probability to be picked), and would like to know the expectation of [gcd(x1, x2,…,xm)]k.

Note that gcd(x1, x2,…,xm) is the greatest common divisor of {x1, x2,…,xm}.


Input

There are multiple test cases. The first line of input contains an integer T indicating the number of test cases. For each test case:

The first line contains two integers n, k (1 ≤ n, k ≤ 106). The second line contains n integers a1, a2,…,an (1 ≤ ai ≤ 106).

The sum of values max{ai} for all the test cases does not exceed 2000000.

Output

For each case, if the expectation is E, output a single integer denotes E · (2n - 1) modulo 998244353.

Sample Input
1
5 1
1 2 3 4 5
Sample Output
42

题意:

给你n个数{a1,a2,a3……an},让我们这个集合的任意子集的最大公因数的k次方求和

思路:

我们可以把题目转换成求gcd等于i的非空集合有多少个,这样就可以直接计算了。

我们在输入的时候存下n个数中的最大的记为Max,因为这n个数的最大公因子一定不可能超过Max,并且记录下每个数的个数

gcd  i从Max枚举到1

对于每个最大公因子i,我们要求出i的倍数的的数有多少,内层循环从i开始,到Max每次+i,成倍增加,因为我们已经记录下每个数的个数了,没有的就是零,这样循环过程直接加就可以得到i的倍数的个数

设n个数中是i的倍数的数有x个。

那么gcd等于i的个数就是总共的 (2^x-1)个(以i为倍数的所有集合)减去gcd等于j的个数,j是i的倍数(也就是最大公因数是i 的倍数的个数),这里相当于一个容斥定理的最简单的情况。

最后的gcd为i的个数dp[i]后,直接求dp[i]*q_pow(i,k)加到答案中取模即可


code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int maxn = 1e6+100;
int sum[maxn];//给定的每个数的个数
int a[maxn];//给定的数组
int dp[maxn];//dp[i]是以i这个数为最大公因数的数的个数
ll q_pow(ll a,ll b){
    ll ans = 1;
    while(b){
        if(b & 1)
            ans = ans * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return ans;
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        memset(sum,0,sizeof(sum));
        memset(dp,0,sizeof(dp));
        int n,k,Max = 0;
        scanf("%d%d",&n,&k);
        for(int i = 0; i < n; i++){
            scanf("%d",&a[i]);
            Max = max(a[i],Max);
            sum[a[i]]++;
        }
        ll ans = 0;
        for(int i = Max; i >= 1; i--){
            int cnt = 0;
            for(int j = i; j <= Max; j += i){
                cnt += sum[j];//记录i的倍数有多少个
                dp[i] = (dp[i] - dp[j] + mod) % mod;
                //计算以i为最大公因数的数有多少,总的i的倍数的集合减去
                //以i的倍数为最大公因数的个数
            }
            dp[i] = ((dp[i] + q_pow(2,cnt) - 1) % mod + mod) % mod;
            ans = (ans + dp[i] * q_pow(i,k)) % mod;//算gcd的k次方,并且有dp[i]个
        }

        printf("%lld\n",ans);
    }
    return 0;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值