因为题解区没法发了,所以在讨论区和大家讨论一下对 PassName & hanss6 的这个题解
的看法,不是讨论区题解(
如果我的理解有误,真诚希望原作者 / 各位大佬批评指正
我认为这个做法(尤其是代码)相当巧妙,但是我在思考这个做法的过程中发现这个算法并非看上去那么容易,而且讨论区有诸多关于正确性的顾虑。实际上这个代码利用了很多 trick,作者对做法的叙述本身带有很强的迷惑性,比如 ai=ai−1 时实际上 lstai=i−1,那么计算 fi 的时候就会调用 fi 本身
那这个会不会产生问题呢?并不会,因为已经赋值了 fi=fi−1,这里实际上计算的是 fi−1+ai。所以如果你“等价的”写作 fi=max(fi−1,flstai+1+⋯) 就会 WA。
接下来是形式化的解释。
首先明确 fi 的定义是 当前考虑 [1,i],与 ai 完全相同的末尾若干个数强制视作一段的开头,这种情况下的 [1,i] 的最佳收益。 比如 ai=7,那 fi 描述的分段就是形如
a1,a2,⋯,ai−k,7,7,⋯,ai=7
我们解释程序的行为为什么符合这个描述。首先 ai=ai−1 时,最优方案肯定是 i 继承 i−1 的颜色,所以程序的行为按照上面引言所述,就是 fi=fi−1+ai。
于是程序会把相同的一段数缩点,接下来不妨设相邻的两个数都是不同的。
fi 代表 i 恰好是新一段的开头,i−1 是上一段的结尾。
a1,a2,⋯,ai−1,ai
接下来就是评论区讨论最多的问题:为什么一定从 lstai+1 处转移?(这里已经有 lstai=i−1)
我们需要分别从最优方案 ai 产生贡献 / 不产生贡献两个角度来说明。
情况 1:如果 ai 产生贡献
那么与 ai 匹配的一定是 lstai 处的值 (∗)。画个图就是
a1,a2,⋯,alst,alst+1,⋯,ai−1,ai
所以转移需要用到的是 flst+1,因为 f 的下标是红色段的开头第一个数。
如若 (∗) 不然,那就是搬出了古早相同值 aold=ai 进行匹配,我们作调整
⇒a1,a2,⋯,aold,⋯,alst,⋯,ai−1,aia1,a2,⋯,aold,⋯,alst,⋯,ai−1,ai
只改变 alst 的颜色,我们实现了收益增大。
情况 2:如果 ai 不产生贡献
那么 fi−1 就是我们需要的答案。这并不是特别 trivial,因为按照 fi−1 的定义我们需要 i−1 独立成段。
a1,a2,⋯,ai−2,ai−1,ai
最优方案为什么一定是这样?我们再用调整法,假设上一个红色段很长
⇒a1,a2,⋯,aX,⋯,ai−2,ai−1,aia1,a2,⋯,aX,⋯,ai−2,ai−1,ai
这样答案不会下降,因为根据“不妨设”,有 ai−2=ai−1,所以 ai−1 原先必没有贡献,而原先 ai 也没贡献,现在一调整,两者只会更好。所以 i−1 一定能独立成段。
最后一个我认为需要解答的点,是为什么这个顶一下,输出的答案是 fn?实际上这个类比“情况 2”不难看出,an 独立成段,一定是最优的。