[R58D] 双胞胎周长

  • 难度 普及+/提高
  • 时限 1s
  • 空限 512m
  • 数学计数

数据规模:1n2×1051 \le n \le 2 \times 10^51ai1091 \le a_i \le 10^9,所有 aia_i33 同余。

思路

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

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

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

退化计数:退化(x+yzx + y \le z)等价于 x+yp/2x + y \le p/2;此时 x+2y2(x+y)px + 2y \le 2(x+y) \le p,故 yzy \le z 自动满足,无需额外约束。于是退化三元组与二元组「1xy1 \le x \le yx+ymx + y \le m」一一对应,其中 m=p/2m = \lfloor p/2 \rfloor:固定 xxyy 可取 xmxx \sim m - xm2x+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) 算出,p21018p^2 \le 10^{18}Int64 范围内;该公式对任意周长成立,与「全部元素模 33 同余」的约定无关。

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

复杂度:时间 O(nlogn)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(): Int64 {
    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)
    return 0
}