新年的巧克力棒

马上就要到羊年了, 羊村一片欢腾, 懒羊羊则懒洋洋地躺在草坪上吃新年的巧克力棒.

他手上的巧克力棒是个由n个巧克力单元格组成的长度为n的长条, 现在懒羊羊想把巧克力棒掰开成一个个小单元格.

初始时懒羊羊会把这根巧克力棒丢在草坪上, 然后每次懒羊羊会从草坪上拿起一根长度大于1的巧克力棒, 然后从某两个相邻的单元格的间隙处掰开变成两根巧克力棒, 然后把这两根巧克力棒丢在草坪上. 懒羊羊初始愉悦值为0. 每次掰开巧克力棒后如果这两根巧克力棒长度相等, 那么懒羊羊将提升1点愉悦值.

当然, 草坪上全是长度为1的巧克力棒时懒羊羊就会停止操作. 现在懒羊羊想知道, 他能获得的愉悦值最多是多少?

说明

题解

  1. 对每一块巧克力棒(长为len), 找到最大的k使得2^k <= len. 将巧克力棒掰开为2^klen-2^k两部分.
  2. 对于2^k的部分, 可以满足每次都能恰好掰开使得两部分长度相等, 从而整体获得2^k-1点愉悦值.
  3. 对于len-2^k的部分:
    • 如果len-2^k01, 则不需要再操作.
    • 否则, 重复步骤1.
fn solution(mut length: u32) -> u32 {
    let mut sat = 0;
    while length > 1 {
        let perf = length.ilog2();
        let perf_part = 2u32.pow(perf);
        sat += perf_part - 1;
        length -= perf_part;
    }
    sat
}
 
#[test]
fn test() {
    let cases = vec![
        (1, 0),
        (3, 1),
        (4, 3),
        (7, 4),
        (233333333, 233333319),
    ];
 
    for case in cases {
        let tobe = case.1;
        let got = solution(case.0);
        println!("f({}) tobe {tobe}, got {got} -- {}", case.0, tobe == got);
    }
}

论证

个人理解, 仅供参考.

转换为等价问题: 初始有n份大小为1单位的材料, 任意两份相同大小的材料可以融合成一份两倍大小的材料, 现对材料进行融合操作直到无法继续, 求最终的融合情况 (即各部分的大小). 可以根据最终的融合情况得到本题所求的最大愉悦值.

等价性

等价求解

结论1: 最终融合情况中无重复值.

结论2: 对于等价问题, 可以证明最终的融合情况的唯一性. (即最终各材料大小排序后组成的有序序列是唯一的)

结论3: 对于n份材料, 所能得到的最大融合材料的大小sizemax2^k (其中2^k <= n2^(k+1) > n).

根据结论2, 可以每次先求解最大融合材料的大小:

结果转换

对于大小为sizei的材料(sizei>1), 将其等分并放回, 重复此操作直到得到sizei份大小为1的材料, 这个过程中的每一次操作都可以获得 1愉悦值.

操作次数op(i) = sizei - 1. 所以最终总的愉悦值为op(1) + op(2) + ... + op(k) = size1 + size2 + ... + sizek - k = n - k (kresult的长度).

观察易得: k 等于 n 的二进制表示中 1 的个数. (使用示例说明)

// 示例1
  0b10101 (n)
= 0b00001 (size1)
+ 0b00100 (size2)
+ 0b10000 (size3)
 
// 示例2
  0b11011 (n)
= 0b00001 (size1)
+ 0b00010 (size2)
+ 0b01000 (size3)
+ 0b10000 (size4)

优化题解

根据结果转换部分的结论, 可以直接求解n的二进制表示中1的个数, 从而得到最终的愉悦值.

fn solution(n: u32) -> u32 {
    n - n.count_ones()
}
 
#[test]
fn test() {
    let cases = vec![
        (1, 0),
        (3, 1),
        (4, 3),
        (7, 4),
        (233333333, 233333319),
    ];
 
    for case in cases {
        let tobe = case.1;
        let got = solution(case.0);
        println!("f({}) tobe {tobe}, got {got} -- {}", case.0, tobe == got);
    }
}

References