Luogu 2467 [SDOI2010]地精部落

挺有意思的题。

优质题解: https://www.luogu.org/blog/user55639/solution-p2467

题意为求长度为n,取值为$[1, n]$的波动序列的个数。

首先需要三个性质:

性质1:在一个波动序列中,如果数字$i$与数字$i - 1$不相邻,那么把$i$与$i - 1$交换之后也会构成一个波动序列

性质2:如果已经构造好了一个波动序列,那么把这个序列中的每一个数$a_{i}$全部变成$(n + 1 - a_{i})$也是一个波动序列,且山谷和山峰的位置相反

性质3:一个波动序列倒过来也是一个合法的波动序列

首先根据性质3,我们只要算出以山峰开头的波动序列的个数,然后乘以二就是最终的答案了。

我们设$f_{i, j}$表示选用区间$[1, i]$中的数,且数字$j$开头为山峰的方案数。

考虑转移:    $f_{i, j} = f_{i, j - 1} + f_{i - 1, i - j + 1}$ $(2leq jleq i )$

解释一下这个转移方程,由于性质1,当数字$j$与数字$j - 1$不相邻的时候,我们直接交换数字$j$和数字$j - 1$可以构造出合法的波动序列

考虑数字$j$与数字$j - 1$相邻的情况,这时候我们构造的序列相当于这样

$(j), (j - 1), ..., ..., ...$

因为$j$一定是一个山峰的位置,那么$j - 1$一定是一个山谷的位置,那么相当于我们要加上数字取值在$[1, j - 1] cup [j + 1, i]$且以数字$j - 1$开头的山谷的数量

我们的状态设计并不能拆分区间,但是我们可以考虑把$[j + 1, i]$区间的数字全部都向左平移一位,这样并不会改变构造的波动序列的合法性

由于性质2,我们所求的这一段答案就是$f_{i - 1, (i - 1) + 1 - (j - 1) = i - j + 1}$。

考虑到1开头作山峰的时候没有合法的波动序列,所以初态从2开始。

滚掉第一维即可。

时间复杂度$O(n ^ {2})$

思维量和码量的差距还是很大的。

Code:

#include <cstdio>
using namespace std;

const int N = 4205;

int n, P, f[2][N];

int main() {
    scanf("%d%d", &n, &P);
    f[0][2] = 1;
    for(int i = 3; i <= n; i++)
        for(int j = 2; j <= i; j++)
            f[i & 1][j] = (f[i & 1][j - 1] + f[(i - 1) & 1][i - j + 1]) % P;
    
    int ans = 0;
    for(int i = 2; i <= n; i++)
        ans = (ans + f[n & 1][i]) % P;
        
    printf("%d
", ans * 2 % P);
    return 0;
}
View Code

我真的不会数数呜呜呜……

原文地址:https://www.cnblogs.com/CzxingcHen/p/9486266.html