ABC 418 F(矩阵优化dp + 线段树维护矩阵乘法)
F
一道融合了很多知识点与模板的好题。
这里只讲述下核心思路,详细思路可以看官解:
根据 \(a\) 数组将茶壶 \(1 \backsim n\) 分成若干段,其中所有的 \(a_{i} \neq -1\) 作为每一段的右端点。显然除了最后一段外,每一段均是一个左开右闭的区间(-1,-1,-1,...,x),且这些区间的长度,以及茶和咖啡的数量均是固定的;而对于最后一段区间,长度固定,但是咖啡的数量没有要求,容易证明其方案数是一个斐波那契数。对于统计合法方案数,除了每个段的内部满足要求,相邻段之间也需要满足临界处两侧不能同时是咖啡。考虑对每一段进行线性 \(dp\)(不考虑最后一段,假设共 \(n\) 段):\(dp_{i}\) 表示只考虑前 \(i\) 段,方案数。发现这样的状态定义很单一,无法继续作转移,因为对于决策当前段,前一段的末尾是咖啡还是茶至关重要。因此重新进行状态定义:\(dp_{i,0/1}\):只考虑前 \(i\) 段,且第 \(i\) 段的末尾是茶/咖啡,方案数。转移过程不多讲述,见官解,我们只关注下转移形式:
- \(dp_{i,0} \leftarrow dp_{i-1,0}\)
- \(dp_{i,0} \leftarrow dp_{i-1,1}\)
- \(dp_{i,1} \leftarrow dp_{i-1,0}\)
- \(dp_{i,1} \leftarrow dp_{i-1,1}\)
若没有修改,直接预处理所有段再跑线性 \(dp\) 足矣。但本题需要点修,因此要考虑用线段树维护上述 \(dp\) 转移过程。
这里就用到了蒟蒻没有接触过的东西——线段树维护区间乘法。其实没有多高大上,只是维护区间数乘变成了维护区间矩阵乘而已,只需要自己搓一个矩阵类,再把 \(pushup\) 部分改改就好了。
对于上述 \(dp\) 转移过程,我们很容易把其化为矩阵形式的转移:
其中 \(f_{i,0/1,0/1}\) 表示:考虑第 \(i\) 段的填充,第 \(i-1\) 段结尾放的是茶/咖啡,且第 \(i\) 段结尾放的是茶/咖啡,方案数。该值可以用组合数快速求出。
初始状态不考虑所有茶壶,即一个空序列,为 \([dp_{0,0}\space\space dp_{0,1}] = [1 \space\space 0]\)(位置 \(0\) 对序列无任何影响,因此可以默认该位置放的是茶;若放咖啡就有影响了)。对于所有段,在其结尾位置维护相应的 \(2 \times 2\) 转移矩阵,而其他位置放置单位矩阵即可。点修只会影响到最多两个矩阵,因此每次修改是 \(O(logn)\) 的。最后我们需要的就是 \([dp_{n,0}\space\space dp_{n,1}]\)。然而这还不是最终答案,因为还没有计入最后一段的转移。
具体细节见代码。