P5999 [CEOI2016] kangaroo 蓝 题解

前言

有些题目照常 DP 不是很好做,感觉像是区间 DP,但是怎么设状态都不好转移,那么可以考虑一种维护块儿的 DP,就是这道题要用到的知识点。

背景分析

如果每次跳跃的点的编号形成一个序列的话,那么这个序列满足“波浪形”,即 \(a_1 < a_2,\ a_2>a_3, \ a_3<a_4 \dots\) 或者 \(a_1>a_2,\ a_2<a_3, \ a_3>a_4 \dots\)

那么转化问题,求 \(1\sim n\) 的所有排列中,满足该排列构成的序列是“波浪形”的排列有几个。

前置知识

该题这种 DP 维护的是一个个块,可以设状态 \(f_{i,j}\) 表示填好了 \(1 \sim n\) 中的数,共有了 \(j\) 个块。

比如:\(f_{7,3}\) 表示前 7 个数中共有 3 个块,这 3 个块里面的信息可以是 \([1][2][3,4,5,6,7]\),也可以是 \([4,1,5][2,3][6,7]\),怎样都行。

每新填一个数,这个数可以开一个新块儿,也可以根据这个数合并之前的块儿,我们具体分析。

加新块

如果加数字 \(i\) 之前已经有了 \(j - 1\) 个块,这些块儿会形成 \(j\) 个空,那么这个数字 \(i\) 单独形成的块儿就可以查到这 \(j\) 个空中的一个,所以 \(f_{i,j} = f_{i-1,j-1} \times j\)

合并块

如果加数字 \(i\) 之前已经有了 \(j + 1\) 个块,这些块儿会形成 \(j+2\) 个空,但是,如果你把数字 \(i\) 放到最前面或者最后面,那么因为最前(后)面没有块儿,就无法合并,所以实际上合法的空只有 \(j\) 个。

所以方程是 \(f_{i,j} = f_{i-1,j+1} \times j\)

这道题目

我们依旧是填数,从 \(1\) 填到 \(n\),那么对于当前数 \(i\),之前填的数都比这个数小,之后填的数都比这个数大,那么加新块、合并块这两个操作都是合法的,无论你放在哪里(此时还不考虑 \(s,t\) 的影响

考虑到 \(s\)\(t\),由于最终的块一定合并成了一个,且以 \(s\) 开头、以 \(t\) 结尾,那么他会影响到加新块的操作。

为什么?

如果 \(s\) 已经填好了,那么 \(s\) 所在的位置必然是第一个块的首部,此时你若加新块,必然不能加到 \(s\) 所在块的前面去,因为这样做会使最后合并的块无法满足 \(s\) 开头

如果 \(t\) 已经填好了,那么 \(t\) 所在的位置必然是最后一个块的尾部,此时你若加新块,必然不能加到 \(t\) 所在块的后面去,因为这样做会使最后合并的块无法满足 \(t\) 结尾

那么加新块的状态转移方程为:\(f_{i,j} = f_{i-1,j-1}(j - [i>s] - [i>t])\)

\([\cdots]\) 表示如果中括号内条件满足返回 1,否则返回 0

\(i>s\) 满足,则其表示填 \(i\) 的时候,\(s\) 已经填好了,那么第一个块的前面就不能再放了,所以要减一,\(i>t\) 同理。

对于合并块的操作无影响。

那么对于 \(i=s,i=t\) 的情况,该怎么办呢?我们拿 \(i=s\) 作说明,\(i=t\) 同理。

\(i=s\),如果要加新块,必然要加到最前面,所以只有一个位置合法,转移方程 \(f_{i,j} = f_{i-1,j-1} \times 1\);如果要合并块,你也只能把 \(s\) 放到第一个块的最前面,这样做块数是不变的,且只有一个合法位置,转移方程 \(f_{i,j} = f_{i-1,j}\)

Code

#include <iostream>
#include <cstring>

using namespace std;

typedef long long LL;
const LL N = 2e3 + 10;
const LL mod = 1e9 + 7;

LL n, s, t;
LL f[N][N];

int main()
{
    cin >> n >> s >> t;
    f[1][1] = 1;
    for (LL i = 2; i <= n; i ++ )
    {
        for (LL j = 1; j <= i; j ++ )
        {
            if (i == s || i == t)
            {
                f[i][j] = (f[i][j] + f[i - 1][j - 1] + f[i - 1][j]) % mod;
                continue;
            }
            f[i][j] = (f[i][j] + f[i - 1][j - 1] * (j - (i > s) - (i > t))) % mod;
            f[i][j] = (f[i][j] + f[i - 1][j + 1] * j) % mod;
        }
    }
    cout << f[n][1] << endl;
    return 0;
}

尾声

自己做不会,看题解也挺蒙的,搞了一下午才明白。

完结撒花!

posted @ 2022-11-18 17:41  LittleMoMol  阅读(22)  评论(0编辑  收藏  举报
*/