[R76E] 灯带
数据规模:,,,,答案对 取模。
思路
只关心颜色的相等关系。 条件「连续三盏灯恰好包含 种颜色」只取决于这三盏灯的颜色是否彼此相等,与颜色具体是几号无关。
于是把方案按「相等关系」拆开:一个配色方案唯一确定下标集合的一个划分——同色的灯放进同一个块,块数就是实际用到的颜色数。反过来,固定一个恰好有 个块的划分,给这 个块各分配一种互不相同的颜色,方案数为下降幂
设 为满足所有 限制、且恰好有 个块的划分数,则答案为 。这样 只出现在最后的下降幂里, 与 无关。
扫描时只需要记住「最后两盏灯是否同色」和已用块数。 依次决定 各自所在的块。当前位置开始的三盏灯中,后两盏 、 的关系只有同色与异色两种,记状态 ;再记已经用掉的块数 。关键点是:块一旦出现就不会消失,之后任何位置都可以回到任意一个旧块,所以候选只有「和 同块」「和 同块」「其余 个块中的某一个」「新开一个块」四类,逐类计数即可,不需要知道更早的位置细节。
- ( 与 同块): 与该块同色时三盏灯只含 1 种颜色, 不变且新状态 ; 取其余 个旧块之一或新开一块时恰好 2 种颜色,新状态 , 分别为不变与加一。故 只有 1 种走法, 有 种( 不变)加 1 种(), 无解。
- ( 与 异块): 与 同块或与 同块都得到 2 种颜色( 不变, 分别为 ); 取 之外的 个旧块之一或新开一块都得到 3 种颜色( 不变或加一,)。故 有 2 种走法, 有 种走法, 无解。
注意 时 已占两个块,所以「其余旧块」是 个;当 时这部分为 ,只剩新开块。
DP。 设 表示前 盏灯划分成 个块、且第 盏与第 盏异色()或同色()的方案数。初始 :两盏灯同色时 ,异色时 。之后每加一盏灯,就按它所在三元组对应的 套用上面的转移。因为 只增不减,而最终必须恰好 个块,所有 的状态可以直接丢弃, 只需枚举到 。
最后 ,再乘下降幂输出。
复杂度
块数只需枚举到 ,转移 ,时间 ,空间 。下降幂的计算是 。
仓颉实现
查看仓颉实现
import std.env.*
import std.convert.*
let MOD: Int64 = 998244353
main() {
let reader = getStdIn()
let head = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let n = head[0]
let m = head[1]
let k = head[2]
let b = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
var dp0 = Array<Int64>(k + 2, { _ => 0 })
var dp1 = Array<Int64>(k + 2, { _ => 0 })
dp1[1] = 1
if (k >= 2) {
dp0[2] = 1
}
var i: Int64 = 0
while (i < n - 2) {
let bv = b[i]
var nd0 = Array<Int64>(k + 2, { _ => 0 })
var nd1 = Array<Int64>(k + 2, { _ => 0 })
var t: Int64 = 1
while (t <= k) {
let v0 = dp0[t]
let v1 = dp1[t]
if (bv == 1) {
nd1[t] = (nd1[t] + v1) % MOD
} else if (bv == 2) {
nd0[t] = (nd0[t] + v1 * (t - 1) + v0) % MOD
nd1[t] = (nd1[t] + v0) % MOD
if (t + 1 <= k) {
nd0[t + 1] = (nd0[t + 1] + v1) % MOD
}
} else {
nd0[t] = (nd0[t] + v0 * (t - 2)) % MOD
if (t + 1 <= k) {
nd0[t + 1] = (nd0[t + 1] + v0) % MOD
}
}
t += 1
}
dp0 = nd0
dp1 = nd1
i += 1
}
var ans = (dp0[k] + dp1[k]) % MOD
var c: Int64 = 0
while (c < k) {
ans = ans * ((m - c) % MOD) % MOD
c += 1
}
println(ans)
}