[R11F] 二维gcd和2
- 难度 提高
- 时限 2s
- 空限 1024m
- 数学欧拉函数数论
数据规模:,答案对 取模。
思路
要求 ,直接枚举 是 ,无法承受。换一个角度,按 的值分类贡献。
定义 为满足 且 且 的二元组 数量。若 ,等价于 ,所以 的二元组个数恰好是 ,答案即为
接下来计算 。除 外,其余互质对可分为 与 两类。引入欧拉函数 表示 中与 互质的数的个数,则 固定时 且互质的 有 个,对称地 同理,于是
预处理出 的前缀和 ,则 ,整个答案可在 内累加。
算法瓶颈在于预处理欧拉函数。 用埃氏筛可以在 内求出所有 :初始化 ,对每个素数 ,把它所有倍数 的 乘以 ,即 。
复杂度:时间 ,空间 。
仓颉实现
import std.env.*
import std.convert.*
main(): Int64 {
let MOD: Int64 = 998244353
let reader = getStdIn()
let n = Int64.parse(reader.readln().getOrThrow())
// 埃氏筛欧拉函数 phi[1..n],用 UInt32 节省内存,无需额外素数表
// 初始化 phi[i]=i,对每个素数 i,把它的所有倍数 j 的 phi[j] *= (1-1/i) = phi[j] - phi[j]/i
var phi = Array<UInt32>(n + 1, { x: Int64 => UInt32(x) })
var i: Int64 = 2
while (i <= n) {
if (Int64(phi[i]) == i) {
// i 是素数
var j = i
while (j <= n) {
phi[j] = UInt32(Int64(phi[j]) / i * (i - 1))
j += i
}
}
i += 1
}
// 原地将 phi[k] 转为前缀和 phisum[k] = sum_{y=2}^k phi[y] % MOD(< MOD 可存 UInt32)
// s[k] = 1 + 2*phisum[k] 为 1..k 中互质对 (i,j) 数量
var run: Int64 = 0
var k: Int64 = 1
while (k <= n) {
if (k >= 2) {
run = (run + Int64(phi[k])) % MOD
}
phi[k] = UInt32(run)
k += 1
}
// 答案 = sum_{x=1}^n x * s[floor(n/x)]
// x 与 s 都 < MOD,直接相乘会溢出 Int64,先 (x % MOD) * sval % MOD
var ans: Int64 = 0
var x: Int64 = 1
while (x <= n) {
let q = n / x
let sval = (1 + 2 * Int64(phi[q])) % MOD
ans = (ans + (x % MOD) * sval) % MOD
x += 1
}
println(ans)
return 0
}
要点:
- 按 的值分类: 的对数就是 范围内互质对数 ,把 降维到对 的 求和。
- 互质对数用欧拉函数前缀和表示:,关键是把 单独算,其余按 、 对称翻倍。
- 欧拉函数用埃氏筛 求出,利用 是积性函数,对素数 的倍数 执行 。
- 内存优化: 最大 ,数组用
UInt32( 且取模后 都能存下),把 压缩到 ,并省去单独的素数表,避免内存超限。 - 防溢出:前缀和与答案累加全程对 取模;计算 时先
x % MOD再相乘,避免Int64溢出;筛法中的乘法通过Int64(phi[j])临时提升精度后再写回UInt32。