[R80D] 三元组计数


数据规模

3≤n≤2×1053 \le n \le 2\times 10^5,1≤ai≤51 \le a_i \le 5。答案不超过 (n3)≈1.33×1015\binom{n}{3}\approx 1.33\times 10^{15},需要用 64 位整数保存。

思路

固定中间下标。 三个下标 i<j<ki<j<k 里,jj 的位置把序列分成左右两段,条件只关心左边取哪个值、右边取哪个值,与它们的相对位置无关。于是枚举 jj,设 y=ajy=a_j,令 LxL_x 为 jj 左侧值等于 xx 的元素个数,RzR_z 为 jj 右侧值等于 zz 的元素个数。

由于 i<j<ki<j<k,左侧每个值为 xx 的位置都能与右侧每个值为 zz 的位置配对,配对数就是 Lx⋅RzL_x\cdot R_z。再套上 ai>ak>aja_i>a_k>a_j 与整除条件,jj 的贡献为

∑1≤x,z≤5x>z>y(x+z)mody=0Lx⋅Rz.\sum_{\substack{1\le x,z\le 5\\ x>z>y\\ (x+z)\bmod y=0}} L_x\cdot R_z.

维护左右频次。 先扫一遍序列,把每个值的总出现次数记到 RR 里。随后从左到右扫 jj:进入 jj 时先令 RajR_{a_j} 减一,此时 RR 恰好统计 jj 右侧,LL 恰好统计 jj 左侧,算出贡献后再令 LajL_{a_j} 加一,把 jj 交给下一步的左侧。

值域只有 55 的好处。 内层只需枚举 x,z∈[1,5]x,z\in[1,5] 且 x>z>yx>z>y 的组合,至多 2525 种,每种用一次乘法和一次取模就能判定,无需任何数据结构。注意 yy 与 x,zx,z 相同时不满足严格不等式,比较时不能用等号。

复杂度

每个下标只做 O(52)O(5^2) 次检查,时间 O(25n)=O(n)O(25n)=O(n);除读入的序列外只用了两个长度为 66 的计数数组,额外空间 O(1)O(1)。

仓颉实现

查看仓颉实现
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)
}