[语言月赛202302] 最澄澈的空与海 Hard Version 题解
Source & Knowledge
2023 年 2 月语言月赛,由洛谷网校入门计划/基础计划提供。
文字题解
题目大意
多测,
解析
本题做法可能有很多种,这里提供一种做法。
以下先给出两个引理,方便解释后面的做法。
引理 1:对于一个满足条件的整数三元组
证明:
因为整数三元组
(x, y, z) 满足上述条件,所以(x - \dfrac yz) - (\dfrac{x - y}{z}) = n! - \dfrac{n!}n ,即x \times \dfrac{z - 1}{z} = (n - 1)! \times (n - 1) 。即
x = (n - 1)! \times (n - 1) \times \dfrac{z}{z - 1} 。显然,接下来
y = x - z \times (n - 1)! 。证毕。
引理 2:在
证明:
由引理 1,当
z \geq 2 时,x = (n - 1)! \times (n - 1) \times \dfrac{z}{z - 1} ,y = x - z \times (n - 1)! 。此时由于(n - 1)! > 0 ,n - 1 \geq 0 ,z - 1 \geq 1 ,那么我们就可以保证x \geq 0 。如果此时x, y 都是整数,那么这个整数三元组就符合题面要求的所有约束。考虑如何保证
x, y 都是整数。不难发现如果x 是整数,y 则一定是整数。因此考虑x 的问题。注意到导致
x 不是整数的唯一因素是分母z - 1 。因此如果想让x 是整数,只要保证(n - 1)! \times (n - 1) \times {z} 可以整除z - 1 即可。又
\gcd(z, z - 1) = 1 (易证,如果k 是z 和z - 1 的因数,那么k 就是z - z + 1 = 1 的因数,所以k 只能是1 ),所以如果(n - 1)! \times (n - 1) \times {z} 可以整除z - 1 ,则只可能为z - 1 是(n - 1)! \times (n - 1) 的因数。对充分性,如果
z - 1 是(n - 1)! \times (n - 1) 的因数,那么x = (n - 1)! \times (n - 1) \times \dfrac{z}{z - 1} 就是非负整数,y 同样也是整数。且由引理 1,如果三元组合法,那么当z 确定时,对应的x, y 是唯一的。因此一个整数三元组(x, y, z) 可以满足上述条件。证毕。
由上面的引理,我们可以发现,对于每个
因此,满足条件的整数三元组的数量,就是
根据唯一分解定理和约数个数定理,我们可以将问题转化为求解
首先考虑单组数据的做法。定义一个整数数组
上述过程完成后,设
对于上述的整套流程,时间上的瓶颈在质因数分解部分。考虑优化质因数分解过程。
考虑线性筛质数的原理。线性筛质数过程中,每个合数都只被标记一次。在这唯一一次标记的过程中,这个合数是被其最小的质因数标记的。在标记的同时,我们考虑记录下这个最小的质因数。
我们使用
我们首先进行一次线性筛质数,同时记录下
然而多测的总时间复杂度为
不难发现我们对
如果只是询问
考虑使用一个变量
由于
但是本题在
这样我们即可在
最后有一个特殊情况需要处理。对于引理 2,我们只证明了
那如果我非要让
会得到
所以,当
所以当且仅当 inf。
代码
标答代码写的很丑陋(诸如没有采用线性求逆元),洛谷上开启 O2 后单个点在 700ms 左右通过,所以考虑后时限设置为两秒。
#include <bits/stdc++.h>
using namespace std;
#define lint long long
#define rep(_, __, ___) for (int _ = __; _ <= ___; ++_)
const int maxn = 1e6 + 5;
const int modint = 998244353;
using pii = pair<int, int>;
namespace FactorSieve {
int prime[maxn];
int ans[maxn];
int p[maxn];
int cnt;
int f[maxn];
lint fpow(lint x, lint y) {
lint ans = 1;
while (y) {
if (y & 1) {
ans = (ans * x) % modint;
}
x = (x * x) % modint;
y >>= 1;
}
return ans;
}
lint finv(lint x) {
return fpow(x, modint - 2);
}
pii v[maxn];
int vcnt = 0;
void init() {
for (int i = 2; i <= 1000000; i++) {
if (!p[i]) {
p[i] = i, prime[++cnt] = i;
}
for (int j = 1; j <= cnt && i * prime[j] <= 1000000; j++) {
p[i * prime[j]] = prime[j];
if (p[i] == prime[j])
break;
}
}
lint cur = ans[0] = 1;
for (int i = 1; i <= 1000000; ++i) {
vcnt = 0;
lint var = i;
while (var != 1) {
int cnt = 0, cur = p[var];
while (var % cur == 0)
var /= cur, ++cnt;
v[++vcnt] = make_pair(cur, cnt);
}
for (int k = 1; k <= vcnt; ++k) {
pii j = v[k];
cur *= finv(1 + f[j.first]);
cur %= modint;
f[j.first] += j.second * 2;
cur *= 1 + f[j.first];
cur %= modint;
}
ans[i] = cur;
for (int k = 1; k <= vcnt; ++k) {
pii j = v[k];
cur *= finv(1 + f[j.first]);
cur %= modint;
f[j.first] -= j.second;
cur *= 1 + f[j.first];
cur %= modint;
}
}
}
int query(int x) {
return ans[x];
}
};
int main() {
FactorSieve::init();
int t;
scanf("%d", &t);
while (t--) {
int n;
scanf("%d", &n);
if (n == 1)
printf("inf\n");
else
printf("%d\n", FactorSieve::query(n - 1));
}
return 0;
}