D. Count GCD(数论/gcd/素数筛/容斥)

题目
参考

题意

给定n个数ai,1<=ai<=m
构造数组bi,使得gcd(b1,b2,…bi)==ai,1<=bi<=m
问这样的数组bi有多少个,答案对998244353 取模。

思路

首先b1只能取a1
对于i>1,要满足gcd(b1,b2,…bi)==ai,a[i-1]必然可以被a[i]整除,所以,如果存在a[i-1]不能被ai整除,答案为0;
对于i>1,我们要找bi,使得gcd(a[i-1],bi)==ai,bi<=m,化简为gcd(a[i-1]/ai,bi/ai)==1, bi/ai<=m/ai
即求x的个数,使得gcd(a[i-1]/ai,x)==1, x<=m/ai

求一个范围内,与数v互素的个数,我们可以用容斥来解决。
令v=p1^h1+p2^h2+…+pk^hk,其中p1,p2,…,pk为互不相同的素数,hi为它们的幂次。
则x的个数为
+(与1互素的个数)
-(与p1*p2*…*pk/pi互素的个数)
+(与p1*p2*…*pk/(pi*pj)互素的个数)
-…

详见代码

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pcc pair<char, char>
#define pii pair<int, int>
#define inf 0x3f3f3f3f
const int maxn = 200010;
const int mod = 998244353;

int sub_mod(int x, int y) {
	ll res = 1LL * x % mod - (y % mod);
	res = (res + mod) % mod;
	return res;
}
int add_mod(int x, int y) {
	return (1LL * x + y) % mod;
}
int n, m;
int a[maxn];
vector<int> p;
// 预处理a[0]的所有素数 
void get_prime(int n) {
	p.clear();
	int x = 2;
	while (x * x <= n) {
		if (n % x == 0) {
			p.push_back(x);
			while (n % x == 0) {
				n /= x;
			}
		}
		++x;
	}
	if (n > 1) {
		p.push_back(n);
	}
}

int cal(int x, int limit) {
	vector<int> px;
	// 因为 x能整除 a[0] 我们从a[0]的素数即可找出x的素数 
	for (auto v: p) {
		if (x % v == 0) {
			px.push_back(v);
		}
	}
	int sz = px.size();
	int ans = 0;
	// 枚举所有的素数组合 
	for (int mask = 0; mask < (1 << sz); ++mask) {
		int cur = 1, cnt = 0;
		for (int i = 0; i < sz; ++i) {
			if (mask & (1 << i)) {
				cur *= px[i];
				++cnt;
			}
		}
		if (cnt & 1) { // 素数出现奇数次 减去贡献 
			ans = sub_mod(ans, limit / cur);
//			ans -= limit / cur;
		} else { // 素数出现偶数次 加上贡献 
			ans = add_mod(ans, limit / cur);
//			ans += limit / cur;
		}
	}
	return ans;
}

void solve() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i) {
    	scanf("%d", &a[i]);
	}
	bool ok = true;
	for (int i = 1; i < n; ++i) {
		if (a[i-1] % a[i]) {
			ok = false;
			break;
		}
	}
	if (!ok) {
		printf("0\n");
		return;
	}
	get_prime(a[0]);
	ll ans = 1;
	for (int i = 1; i < n; ++i) {
		ans = ans * cal(a[i-1] / a[i], m / a[i]) % mod;
	}
	printf("%lld\n", ans);
}
int main() {
    int t = 1;
    scanf("%d", &t);
    int cas = 1;
    while (t--) {
//		printf("cas %d:\n", cas++);
        solve();
    }

}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值