连续段 Dp
连续段 Dp
简介
求个满足条件的排列数个数,存在一些例如 \(a_i \lt a_j\) 的限制条件
假如我们记录哪些数出现过,那么显然状态会爆炸,无法记录
我们可以从大到小,或从小到大来填数
状态转移的过程可能与相邻的已插入元素的具体信息相关,如插入一个新元素时,需要知道与其插入位置相邻的两个元素的值是多少
基本操作
它的元素插入操作只会在连续段的两端进行,通过新建连续段,插入至已有连续段的两端,合并两连续段三类转移方式来进行状态转移
每一段内部都保证合法,相当于是 \([1,n]\) 这个问题的子问题,也可以认为是考虑这个小区间的答案,扩展到 \([1,n]\) 大区间
\(f_{i,j}\) 表示考虑前 \(i\) 个元素,分为 \(j\) 个连续段的方案数
- 新建连续段
元素 \(i\) 构成的新段可以放在 \(j-1\) 个段中间的任意位置和最前面最后面
- 插入到已有段两端
元素 \(i\) 个进入任意段
- 合并两连续段
连续段的合并等操作均在连续段的两端进行,由于我们是按特定顺序依次插入元素的,因此端点的信息是便于维护的
考考虑仅对于一个连续段,其在端点处的插入操作
考虑对于两个连续段,其合并操作是怎样的
最后用数学归纳法汇总
例题
转化题目意思
求满足 \(p_1 \lt p_2 \gt p_3 \lt p_4...\),\(p_1=s,p_n=t\) 的排列数
考虑连续段 \(dp\),从小到大填数,即当前填的数比任意填好的数大
分类讨论
当前数 \(\not=s,\not=t\)
- 新建连续段
后加入的数一定大于当前数,注意判断 \(i\gt s\),和 \(i\gt t\),\(s,t\) 如果之前在,这两个的位置是不能动的
- 插入到已有段两端
它因为不是第一个数或者最后一个数一定有一段是比它小的,而另一端又比它大,不满足
- 合并两连续段
当前数两端的都比小,满足条件
否则 $$f_{i,j} \leftarrow f_{i-1,j-1}+f_{i-1,j}$$
新建连续段,只有一个位置合法 \(f_{i-1,j-1} \times 1\),因为它一定是最前面或者最后面,可以完成操作 \(2\) 插入,但是只有一个位置合法
最后答案即为 \(f_{n,1}\)
const int N=2e3+10,Q=1e9+7;
int n,S,T;
int f[N][N];
signed main()
{
n=fr(),S=fr(),T=fr();
f[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
{
if(i==S || i==T) f[i][j]=(f[i-1][j-1]+f[i-1][j])%Q;
else f[i][j]=((j-(i>S)-(i>T))*f[i-1][j-1]%Q+j*f[i-1][j+1]%Q)%Q;
}
fw(f[n][1]);
return 0;
}

浙公网安备 33010602011771号