P9007 [入门赛 #9] 最澄澈的空与海 (Hard Version)
题解里头似乎没有离线的做法。实际上,用离线来做,不仅更快,而且更好写。
题意即求
的
前两式相减,消去
当
当
于是
显然这玩意儿没法直接算。但我们知道,一个数的因子数,就是它唯一分解后质因子指数的任意组合,也即
于是现在的问题,就转化为怎么快速做出
对于单个
但本题单单是根号分解就已经过不去了。
可以考虑线性筛筛出每个数的最小质因子,然后从最小质因子开始做分解;于是,复杂度从根号降到对数。
这样,我们就知道了怎么在
下面考虑对于
非常显然地,对于
于是我们可以考虑离线,然后排序,这样就可以消除无用的计算。维护一个
代码实现很简单。
#include <iostream>
#include <algorithm>
#include <bitset>
#define fi first
#define se second
#define int long long
using namespace std;
using pi = pair<int, int>;
const int N = 1e6 + 10, P = 998244353;
int mn[N], primes[N], res[N], s[N], inv[N], nr = 0;
pi z[N];
// 线性筛求最小质因子
void sieve (int n)
{
for (int i = 2; i <= n; i++) {
if (!mn[i]) ++nr, mn[primes[nr] = i] = nr;
for (int j = 1; primes[j] * i <= n; j++) {
mn[i * primes[j]] = j;
if (i % primes[j] == 0) break;
}
}
}
signed main (void)
{
int m;
cin.tie (0)->sync_with_stdio (false);
// 离线并开筛
cin >> m;
for (int i = 1; i <= m; i++) cin >> z[i].fi, --z[i].fi, z[i].se = i;
sort (z + 1, z + m + 1), sieve (z[m].fi);
// 线性预处理逆元
inv[1] = 1;
for (int i = 2; i <= z[m].fi; i++) inv[i] = (P - P / i) * inv[P % i] % P;
int i = 1, ans = 1;
// 特判无解情况
while (!z[i].fi && i <= m) res[z[i++].se] = -1;
for (int u = 2; i <= m; i++) {
// 筛掉 (n - 1)! 中的质因子,并动态维护 ans
for (; u <= z[i].fi; u++) {
int v = u;
while (v > 1) {
int w = mn[v], cnt = 0;
while (v % primes[w] == 0) v /= primes[w], cnt++;
ans = ans * inv[s[w] + 1] % P * (s[w] + cnt + 1) % P, s[w] += cnt;
}
}
// 筛 (n - 1) 项,更新到当前询问的 ans 上
int prod = ans, val = z[i].fi;
while (val > 1) {
int w = mn[val], cnt = 0;
while (val % primes[w] == 0) val /= primes[w], cnt++;
prod = prod * inv[s[w] + 1] % P * (s[w] + cnt + 1) % P;
}
res[z[i].se] = prod;
}
for (int i = 1; i <= m; i++)
if (res[i] == -1) cout << "inf\n";
else cout << res[i] << '\n';
return 0;
}