[R64E] 4的倍数
- 难度 普及+/提高
- 时限 1s
- 空限 512m
- 组合计数逆元
数据规模:。
思路
关键性质: 一个十进制整数是否为 4 的倍数,只取决于它的最后两位。设 ,其中 、 分别是倒数第二位和最后一位。由于 ,所以 ,百位及更高位无论怎么选都不影响结果。
分两种情况:
- :长度为 1 的子序列就是单个数字,统计字符为
0、4、8的位置数量即可。 - :从右往左扫描,枚举倒数第二位的位置 ,并维护 表示位置 右侧数字 的出现次数。设 ,最后一位数字 需要满足 。确定最后两位在位置 与右侧某个数字 的位置后,前面的 个位置要从 左侧(共 个字符)任选,方案数为 ,因此这一位的贡献为 。把当前 所有合法 的贡献累加入答案后,再令 ,表示当前位置也可以作为更左侧位置的最后一位。
组合数通过阶乘 + 逆元预处理,模 下 求出;当 时组合数视为 0(代码中直接用条件跳过)。
复杂度
时间复杂度 ,空间复杂度 。
仓颉实现
import std.convert.*
import std.env.*
const MOD: Int64 = 1000000007
func powMod(a: Int64, e: Int64): Int64 {
var res: Int64 = 1
var base = a
var exp = e
while (exp > 0) {
if (exp % 2 == 1) {
res = res * base % MOD
}
base = base * base % MOD
exp = exp / 2
}
return res
}
main() {
let reader = getStdIn()
let nk = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let n = nk[0]
let k = nk[1]
let s = reader.readln().getOrThrow()
let a = Array<Int64>(n, { _ => 0 })
var i: Int64 = 0
while (i < n) {
a[i] = Int64(s[i]) - 48
i += 1
}
var ans: Int64 = 0
if (k == 1) {
i = 0
while (i < n) {
if (a[i] == 0 || a[i] == 4 || a[i] == 8) {
ans += 1
}
i += 1
}
println(ans)
return
}
let fac = Array<Int64>(n + 1, { _ => 1 })
var f: Int64 = 1
i = 1
while (i <= n) {
f = f * i % MOD
fac[i] = f
i += 1
}
let invFac = Array<Int64>(n + 1, { _ => 1 })
invFac[n] = powMod(fac[n], MOD - 2)
var j = n
while (j >= 1) {
invFac[j - 1] = invFac[j] * j % MOD
j -= 1
}
let cnt = Array<Int64>(10, { _ => 0 })
let ky = k - 2
i = n - 1
while (i >= 0) {
let v = a[i]
var ways: Int64 = 0
if (i >= ky) {
ways = fac[i] * invFac[ky] % MOD * invFac[i - ky] % MOD
}
var d: Int64 = 0
while (d < 10) {
if ((10 * v + d) % 4 == 0) {
ans = (ans + cnt[d] * ways) % MOD
}
d += 1
}
cnt[v] += 1
i -= 1
}
println(ans)
}