[R44E]路径数为K
数据规模:,,构造的图要求 且无重边。
思路
要求构造一个点数不超过 50 的 DAG,使得从顶点 到顶点 的路径数恰好为 。直接按二进制逐位倍增(每层两个节点)需要约 60 个点,超过 50 的限制,因此改用斐波那契拆解:每层只用一个节点,路径数以斐波那契速度增长。
构造一条链 :
- 就是起点 ,规定路径数 ;
- 只从 连过来,;
- 对 , 从 和 各连一条入边,于是 ,即 是斐波那契数 。
每个 再连一条直达汇点 的边,就会贡献 条路径。因此只要把 表示成若干互不相同的斐波那契数之和,给这些层级加上直达 的边即可。用从大到小的贪心(齐肯多夫分解)就能得到这样的表示:
由于 ,最高只用得到第 43 层,总点数不超过 。且所有边都从编号小的点指向编号大的点,图天然是 DAG,也没有重边。
复杂度
每个测试点生成约 44 个斐波那契数并做一次贪心,时间 ,输出规模 。
仓颉实现
查看仓颉实现
import std.env.*
import std.convert.*
// 斐波那契链构造:c_0、c_1 的路径数 f = 1,之后 f(c_i) = f(c_{i-1}) + f(c_{i-2})
// 对 K 做贪心(齐肯多夫)分解:选中层级 i 就加一条 c_i -> t 的边
// 总路径数 = 所有选中层级 f 值之和 = K;点数 = maxLevel + 2 <= 45 <= 50
func build(reader: ConsoleReader): Unit {
let k = Int64.parse(reader.readln().getOrThrow())
// 生成不超过 1e9 的斐波那契数列:fib[0] = fib[1] = 1
var fib = Array<Int64>(60, { _ => 0 })
fib[0] = 1
fib[1] = 1
var len: Int64 = 2
while (len < 60 && fib[len - 1] + fib[len - 2] <= 1000000000) {
fib[len] = fib[len - 1] + fib[len - 2]
len += 1
}
// 从大到小贪心选择层级
var chosen = Array<Bool>(len, { _ => false })
var rem = k
var j = len - 1
while (j >= 0) {
if (rem > 0 && fib[j] <= rem) {
chosen[j] = true
rem -= fib[j]
}
j -= 1
}
var maxLevel: Int64 = 0
var l: Int64 = 0
while (l < len) {
if (chosen[l]) {
maxLevel = l
}
l += 1
}
let t = maxLevel + 2
// 统计边数
var cnt: Int64 = 0
if (maxLevel >= 1) {
cnt += 1
}
var lv: Int64 = 2
while (lv <= maxLevel) {
cnt += 2
lv += 1
}
l = 0
while (l < len) {
if (chosen[l]) {
cnt += 1
}
l += 1
}
println("${t} ${cnt}")
if (maxLevel >= 1) {
println("1 2")
}
lv = 2
while (lv <= maxLevel) {
println("${lv} ${lv + 1}")
println("${lv - 1} ${lv + 1}")
lv += 1
}
l = 0
while (l < len) {
if (chosen[l]) {
println("${l + 1} ${t}")
}
l += 1
}
}
main() {
let reader = getStdIn()
let t = Int64.parse(reader.readln().getOrThrow())
var i: Int64 = 0
while (i < t) {
build(reader)
i += 1
}
}