[R80D] 三元组计数
数据规模
,。答案不超过 ,需要用 64 位整数保存。
思路
固定中间下标。 三个下标 里, 的位置把序列分成左右两段,条件只关心左边取哪个值、右边取哪个值,与它们的相对位置无关。于是枚举 ,设 ,令 为 左侧值等于 的元素个数, 为 右侧值等于 的元素个数。
由于 ,左侧每个值为 的位置都能与右侧每个值为 的位置配对,配对数就是 。再套上 与整除条件, 的贡献为
维护左右频次。 先扫一遍序列,把每个值的总出现次数记到 里。随后从左到右扫 :进入 时先令 减一,此时 恰好统计 右侧, 恰好统计 左侧,算出贡献后再令 加一,把 交给下一步的左侧。
值域只有 的好处。 内层只需枚举 且 的组合,至多 种,每种用一次乘法和一次取模就能判定,无需任何数据结构。注意 与 相同时不满足严格不等式,比较时不能用等号。
复杂度
每个下标只做 次检查,时间 ;除读入的序列外只用了两个长度为 的计数数组,额外空间 。
仓颉实现
查看仓颉实现
import std.env.*
import std.convert.*
main() {
let reader = getStdIn()
let n = Int64.parse(reader.readln().getOrThrow())
let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let L = Array<Int64>(6, { _ => 0 })
let R = Array<Int64>(6, { _ => 0 })
for (v in a) {
R[v] += 1
}
var ans: Int64 = 0
for (j in 0..n) {
let y = a[j]
R[y] -= 1
for (x in 1..=5) {
if (x <= y) {
continue
}
for (z in 1..=5) {
if (z <= y || z >= x) {
continue
}
if ((x + z) % y == 0) {
ans += L[x] * R[z]
}
}
}
L[y] += 1
}
println(ans)
}