题目传送门:【BZOJ 1925】
题目大意: 我们认为 n 个位置的高度形成了 1 到 n 的一个排列,这个排列要么满足奇数项的高度比相邻位置都大,要么满足偶数项的高度比相邻位置都大(即:一高一低型)。给定 n,求出符合条件的排列数对 P 取模的值。(3 ≤ n ≤ 4200,P ≤ 10 9 )
题目分析:
这道题是我认为非常好的题了,它的解题关键就在于想到 DP/递推转移方程式。
如题,看到这道题,我们有可能一眼会以为它是一道 DP/递推 水题,于是想当然地写出这样的定义:dp
i,j
表示当前已经解决到第
i
个数,这一位数是
于是我们换一种思路。考虑到前
i
个数为 1 ~
如果前 i−1 个数形成了 1 ~ (i−1) 的排列,设最后那一个数为 x;那么对于第 i 个数,如果现在的高度要上升,我们可以尝试着加入比 x 大的数 x’,遇到前面有比 x’ 大的数就全部 +1,这样也是一种合法的排列。如果现在的高度要下降,那么此时我们就加入比 x 小的数 x”,然后遇到前面有比 x” 大的数也全部 +1。可以证明,这样做是正确的。
那么 DP/递推方程就容易写出来了:dp
其中,当现在的高度要上升时,k = j~i;当现在的高度要下降时,k = 1~(j-1)。
我们利用前缀和/后缀和的性质,可以将 k 给优化掉,由开始的 O(n 3 ) 优化到 O(n 2 )。
本题卡空间,需要使用滚动数组
下面附上代码:
- #include<cstdio>
- #include<cstring>
- #include<algorithm>
- using namespace std;
- typedef long long LL;
- const int MX=4205;
- int n,mod;
- int dp[2][MX],ans=0;
- //dp(i,j): 前 i 个数形成的排列,末位数为 j 的总方案数
- void solve(){
- int f=1;
- dp[1][1]=1;
- //奇数位下降,偶数位上升;这里 f=1 为下降,f=0 为上升
- for (int i=2;i<=n;i++){
- f^=1;
- if (f){ //奇数下降
- for (int j=1;j<i;j++){
- dp[1][j]=(dp[1][j]+dp[0][j])%mod;
- }
- memset(dp[0],0,sizeof(dp[0]));
- for (int j=1;j<=i;j++){
- dp[1][j]=(dp[1][j]+dp[1][j-1])%mod;
- }
- } else { //偶数上升
- for (int j=2;j<=i;j++){
- dp[0][j]=(dp[0][j]+dp[1][j-1])%mod;
- }
- memset(dp[1],0,sizeof(dp[1]));
- for (int j=i;j>=1;j–){
- dp[0][j]=(dp[0][j]+dp[0][j+1])%mod;
- }
- }
- }
- }
- int main(){
- scanf(”%d%d”,&n,&mod);
- n–;
- solve();
- for (int i=1;i<=n;i++){
- ans=(ans+dp[n&1][i])%mod;
- }
- ans=(ans+ans)%mod;
- if (n==0) ans=1%mod;
- printf(”%d”,ans);
- return 0;
- }