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;
}