[R13E] 合成球2

  • 难度 普及-
  • 时限 1s
  • 空限 512m
  • 计数快速幂费马小定理

数据规模:1n101061 \le n \le 10^{10^6}(以十进制串给出),2k1062 \le k \le 10^6

思路

与给定颜色序列、问合成方案数的题不同,本题问的是:每个球颜色任意取 1k1\sim k,有多少种初始情况最终能合成出颜色 11

关键是合成规则——新球颜色由你从合成前的两个球颜色中任选。于是:

  • 只要初始情况中 至少有一个颜色为 11 的球,每轮合成时都把颜色 11 保留下来,最后一定得到颜色 11
  • 反之,若初始全是 2k2\sim k,则任你怎么合成都产生不出颜色 11

所以答案是「所有初始情况」减去「全不为 11 的初始情况」:

ans=kn(k1)n(mod998244353)\text{ans}=k^n-(k-1)^n \pmod{998244353}

大指数取模

nn 以最多 10610^6 位的十进制串给出。模数 P=998244353P=998244353 是质数,kkk1k-1 都与 PP 互质,由费马小定理 aP11(modP)a^{P-1}\equiv 1\pmod P

ananmod(P1)(modP)a^n \equiv a^{\,n\bmod(P-1)}\pmod P

于是把 nn 当字符串逐位算出 y=nmod(P1)y=n\bmod(P-1)(每一位 y=(y*10+d) mod (P-1)),再用快速幂分别算 kyk^y(k1)y(k-1)^y 即可。逐位取模是 O(n)O(|n|),快速幂是 O(logP)O(\log P)

注意 n=1n=1(k1)1=k1(k-1)^1=k-1,公式仍然成立,无需特判;减法结果加模取模避免负数。

仓颉实现

import std.convert.*
import std.env.*

let MOD: Int64 = 998244353

func qpow(base: Int64, exp: Int64): Int64 {
    var b = base % MOD
    if (b < 0) {
        b = b + MOD
    }
    var e = exp
    var r: Int64 = 1
    while (e > 0) {
        if (e % 2 == 1) {
            r = r * b % MOD
        }
        b = b * b % MOD
        e = e / 2
    }
    return r
}

main(): Int64 {
    let reader = getStdIn()
    let line = reader.readln().getOrThrow()
    let parts = line.split(" ", removeEmpty: true)
    let nStr = parts[0]
    let k = Int64.parse(parts[1])
    // y = n mod (MOD-1),n 是超大十进制串
    let phi = MOD - 1
    var y: Int64 = 0
    for (ch in nStr) {
        let d = Int64(UInt32(ch) - UInt32(r'0'))
        y = (y * 10 % phi + d % phi) % phi
    }
    let kk = k % MOD
    let kk1 = (k - 1) % MOD
    let ans = (qpow(kk, y) - qpow(kk1, y) + MOD) % MOD
    println("${ans}")
    return 0
}

要点:

  • 题眼是看出「存在颜色 11 即可合成出颜色 11」,把计数问题化成总数减补集。
  • 超大 nn 不能转整数,要用费马小定理把指数降到 P1P-1 范围内,逐位取模。
  • for (ch in nStr) 得到的是 UInt8 字节,先经 UInt32Int64 转成数字。