Loading

Educational Codeforces Round 138 D. Counting Arrays(数论/计数原理)

题意:

对于一个数组\(a\),可以删除\(a[i]\)当且仅当\(gcd(i, a[i]) = 1\),删除后这个位置后面的数将向前平移。现在给定\(n\)\(m\),问有多少个长度不超过\(n\),元素不超过\(m\)的数组,存在不止一种的清空方式。

考虑长度固定为\(n\)时的答案。正难则反,可以求出总数量然后减去只有一种清空方式的数组个数。首先可以注意到第一个位置的数无论什么时候都是可以清空的,那么一种方式是每次都删掉第一个位置的数,这对于任何数组都是一种合法的方案,那么就需要保证,在任何时候,从第二个位置开始的数都不能被删除,即这个长度为\(n\)的数组一开始要满足:

对于第\(i\)个位置的数\(a[i]\)\(\forall j\in[2, i], gcd(a[i], j) \neq1\)(这是因为如果一直删第一个位置的数,要删\(i - 1\)次才能到\(a[i]\),在这之前\(a[i]\)的位置是\(i\)一直到2都有可能)

这也就是说,\(a[i]\)\(1\)\(i\)中所有质数乘积的倍数且不能大于\(m\)。设质数乘积的倍数是\(mul\),那么合法的\(a[i]\)取值就有\(\lfloor\frac{m}{mul}\rfloor\)个,设这个值是\(val_i\),用乘法原理就能得到长度为\(n\)的只有一种清空方式的数列个数为\(\Pi_{i=1}^nval_i\),因此也就能求出长度为\(1~n\)的只有一种清空方式的数列个数了,用总数去减即可。

需要注意的是,因为\(m\)很大,所以特别需要注意取模以免进行乘法的时候就爆long long。

#include <bits/stdc++.h>
#define ll long long
#define int long long
#define N 1000005
#define mod 998244353
using namespace std;
bool prime[N];
ll fpow(ll a, ll b) {
	ll ans = 1;
	for(; b; b >>= 1) {
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
	}
	return ans;
}
void solve() {
	int n, m;
	cin >> n >> m;
	for(int i = 2; i <= N - 5; i++) {
		if(!prime[i]) {
			for(int j = 2 * i; j <= N - 5; j += i) {
				prime[j] = 1;
			}
		}
	}
	int mul = 1;
	int res = 1;
	int tans = 0;
	for(int i = 1; i <= n; i++) {
		if(!prime[i]) {
			mul = mul * i;
		}
		if(mul > m) break;
		res = (res * ((m / mul) % mod)) % mod;//(m / mul)后要先取模 防止爆ll
		tans = (tans + res);
	}
	int tot = 0;
	int tmp = 1;
	for(int i = 1; i <= n; i++) {
		tmp = tmp * (m % mod) % mod;//要先对m取模 防止爆ll
		tot = (tot + tmp);
	}
	cout << (tot - tans + mod) % mod;
}
signed main() {
	int T = 1;
	// cin >> T;
	while(T--) {
		solve();
	}
	return 0;
}
posted @ 2022-10-21 23:07  脂环  阅读(48)  评论(0编辑  收藏  举报