[R50F]平衡数组
数据规模:,。
思路
数组 一定单调不增,因此平衡数组 也一定单调不增,可以把它看成整数分拆的 Ferrers 图。 正是 Ferrers 图转置后第 行的长度,所以平衡数组恰好对应大小为 的自共轭分拆。一个分拆中不同数字的个数,则等于 Ferrers 图边界上的拐角数。
转化为互异奇数分拆
考虑自共轭分拆主对角线上的格子。设第 个对角线格子右侧有 个格子,由对称性,它下方也有 个格子,对应的对角钩长度为 。所有对角钩互不相交并覆盖整张图,而且 两两不同。
因此,自共轭分拆与一个有限集合 一一对应,其大小为
也就是说,它等价于把 分拆成互不相同的奇数。记方案数生成函数为
为了快速求出所有 ,令
将奇数次幂与偶数次幂的因子分别整理,再使用 Jacobi 三重积恒等式,可得
另一方面,由欧拉五边形数定理,
记
比较 的 系数,得到
其中负下标项视为 。每个 只需枚举 个广义五边形数。
统计拐角数
把集合 中连续的整数合并成若干个最大连续段。每个连续段在自共轭 Ferrers 图上产生一对关于主对角线对称的拐角;如果某个连续段包含 ,这一对拐角会在主对角线上重合。因此,设连续段数为 ,则拐角数为
等价地:选择 贡献一个拐角;对于每个 ,若选择 但不选择 ,就贡献两个拐角。
记所有方案的拐角数之和的生成函数为 。在 中分别强制上述元素被选或不选,有
求和部分可以裂项:
所以它会首尾相消,最终得到
由于 ,比较系数可得
负下标同样视为 。按 从小到大递推即可,最终答案为 。整个过程只有加减法,不要求 为质数。
复杂度
时间复杂度为 ,空间复杂度为 。
仓颉实现
查看仓颉实现
import std.env.*
import std.convert.*
main() {
let reader = getStdIn()
let input = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let n = input[0]
let modulus = input[1]
let size = n + 1
let partitionCount = Array<Int64>(size, { _ => 0 })
let cornerSum = Array<Int64>(size, { _ => 0 })
partitionCount[0] = 1
var squareRoot: Int64 = 1
var nextSquare = 2 * squareRoot * squareRoot
for (current in 1..size) {
var value: Int64 = 0
if (current == nextSquare) {
if (squareRoot % 2 == 1) {
value = -2
} else {
value = 2
}
squareRoot += 1
nextSquare = 2 * squareRoot * squareRoot
}
var k: Int64 = 1
while (true) {
let first = k * (3 * k - 1) / 2
if (first > current) {
break
}
let second = k * (3 * k + 1) / 2
if (k % 2 == 1) {
value += partitionCount[current - first]
if (second <= current) {
value += partitionCount[current - second]
}
} else {
value -= partitionCount[current - first]
if (second <= current) {
value -= partitionCount[current - second]
}
}
k += 1
}
value %= modulus
if (value < 0) {
value += modulus
}
partitionCount[current] = value
var sum = partitionCount[current - 1] - cornerSum[current - 1]
if (current >= 2) {
sum += cornerSum[current - 2]
}
if (current >= 3) {
sum += partitionCount[current - 3] + cornerSum[current - 3]
}
sum %= modulus
if (sum < 0) {
sum += modulus
}
cornerSum[current] = sum
}
println(cornerSum[n].toString())
}