[R58D] 双胞胎周长


数据规模:1≤n≤2×1051 \le n \le 2 \times 10^5,1≤ai≤1091 \le a_i \le 10^9,所有 aia_i 模 33 同余。

思路

设 s(p)s(p) 为周长为 pp 的整数边长三角形个数:三边无序,1≤x≤y≤z1 \le x \le y \le z,x+y+z=px + y + z = p,x+y>zx + y > z。

无序三元组计数:先数允许退化的三元组,即 pp 拆成恰好三个正整数的无序拆分,方案数为

P(p)=⌊p2+312⌋P(p)=\left\lfloor\frac{p^2+3}{12}\right\rfloor

退化计数:退化(x+y≤zx + y \le z)等价于 x+y≤p/2x + y \le p/2;此时 x+2y≤2(x+y)≤px + 2y \le 2(x+y) \le p,故 y≤zy \le z 自动满足,无需额外约束。于是退化三元组与二元组「1≤x≤y1 \le x \le y,x+y≤mx + y \le m」一一对应,其中 m=⌊p/2⌋m = \lfloor p/2 \rfloor:固定 xx 时 yy 可取 x∼m−xx \sim m - x 共 m−2x+1m - 2x + 1 个,求和得

D(p)={k2,m=2kk(k+1),m=2k+1D(p)=\begin{cases}k^2, & m = 2k\\ k(k+1), & m = 2k+1\end{cases}

因此 s(p)=P(p)−D(p)s(p) = P(p) - D(p) 可 O(1)O(1) 算出,p2≤1018p^2 \le 10^{18} 在 Int64 范围内;该公式对任意周长成立,与「全部元素模 33 同余」的约定无关。

统计答案:把每个 sis_i 都算出来,sis_i 相同的下标两两成对。将 ss 值排序后扫描连续相等段,段长为 cc 时贡献 c(c−1)/2c(c-1)/2。

复杂度:时间 O(nlog⁡n)O(n \log n),空间 O(n)O(n)。

仓颉实现

查看仓颉实现
import std.env.*
import std.convert.*
import std.sort.*

// 周长为 p 的整数边长三角形个数:
// 把 p 拆成三个正整数(无序)的方案数 P = floor((p^2+3)/12),
// 其中退化的(x+y <= z)有 D 个:m = floor(p/2),m=2k 时 D=k^2,m=2k+1 时 D=k(k+1)。
func triangles(p: Int64): Int64 {
    let P = (p * p + 3) / 12
    let m = p / 2
    let k = m / 2
    let d = if (m % 2 == 0) { k * k } else { k * (k + 1) }
    return P - d
}

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) })
    var ss = Array<Int64>(a.size, { _ => 0 })
    for (i in 0..a.size) {
        ss[i] = triangles(a[i])
    }
    sort(ss)
    var ans: Int64 = 0
    var i: Int64 = 0
    while (i < ss.size) {
        var j = i
        while (j < ss.size && ss[j] == ss[i]) {
            j++
        }
        let c = j - i
        ans += c * (c - 1) / 2
        i = j
    }
    println(ans)
}