连续段dp小记
是一种比较套路而巧妙的 DP Trick ,最近才刚刚碰到,觉得很有意思就稍微记录一下。
先从一道模板题入手。
CEOI 2006 Kangaroo
简述题意
我们定义一个排列 \(P\) 为合法的,当且仅当:
- \(p_{1}=s,p_{n}=t\)
- 如果 \(p_{i}>p_{i-1}\) ,那么 \(p_{i}>p_{i+1}\)
- 如果 \(p_{i}<p_{i-1}\) ,那么 \(p_{i}<p_{i+1}\)
问有多少合法的 \(P\)。
Solution
考虑把数字从小到大插入到序列中,从而得到一个排列。
定义 \(f_{i,j}\) 表示枚举到了第 \(i\) 个数,已经有了 \(j\) 个连续段的方案数。
那么每次加入数字有两种 Case
-
把两个段合并。
有 \(j-1\) 个位置可以合并。
\(f_{i,j}= f_{i,j} +f_{i-1,j+1} \times (j-1)\)
-
重新创建一个段。
有 \(j+1\) 个空位可以塞,但是注意 \(s\) 在最左边,\(t\) 在最右边,需要特判。
\(f_{i,j}=f_{i,j}+f_{i-1,j-1}\times[j-(j>s)-(j>t)]\)
当然,\(i=s、t\) 的时候也是需要特判的,考虑它们在边界单独开一个段,要么和边界的段合并,所以是:
\(f_{i,j}=f_{i-1,j-1}+f_{i-1,j}\)。
上面这种方式保证了除去 \(s,t\) 所在的段以外每一个段都是 \(010101....10\) 的形式,因此被统计到的每一种方案都是合法的。
把我们插入的这个过程看作是在构建一颗笛卡尔树,我们的每一种方案都恰好唯一对应了一种序列,所以和合法排列都是一一对应的,不会多算、少算。
JOI OPEN 2016 摩天楼
简述题意
求有多少合法的排列 \(P\) 满足 \(\sum^{n}_{i=2}|a_{p_{i}}-a_{p_{i-1}}|\leq L\)。
\(n\leq 100,a_{i},L\leq 1000\)。
Solution
根据套路做法,有一个非常 naive
的DP。
设 \(f_{i,j,k,p}\) 表示插入到第 \(i\) 大的元素,已经有 \(j\) 个连续段,目前的和为 \(k\) ,已经有 \(p\) 个边界位置被占领。
对于每一个数分类讨论:
- 插入边界单独作为一段
- 插入边界和最边上的段合并
- 和一个段合并
- 合并两个段
- 单独作为一个段
通过这种方式可以直接把绝对值拆掉。
这样子是 \(O(n^3V)\) 的,如果实现的而比较精细外加常数比较小的话还是有机会通过的。
考虑换一种统计方法,每过一回合之后所有边界上还有空位的位置所造成的值都会增加一个 \(a_{i}-a_{i-1}\)。
这样就可以做到 \(O(n^2L)\) 了。