P5999 [CEOI2016] kangaroo

题目大意

求出有多少种排列 \(p\) 满足对于任意 \(i\in (1,n)\)\(p_i\) 两侧的值同时大于或小于 \(p_i\)。规定 \(p_1=s,p_n=t\)
\(2\leq n\leq 2\times 10^3,1\leq s,t\leq n\)

思路

首先能看出是动态规划。但是如果纯区间动规的话不太好转移,因为无法使得两个区间拼接的部分仍然满足大小的关系。这时我们考虑一种 trick。考虑 \(dp[i][j]\) 表示插入 \([1,i]\),并将序列分成了 \(j\) 段,满足每一段的合法性。且要求每一次操作任意一段均为合法。

所以考虑:

  1. \(i\) 插入到原来的块里。可这样后续的插入会有一种方案插在 \(i\) 的一侧,这样这个就又大又小不符合题意了。
  2. 新增一个块。
    这个显然可以,因为一个块里只有它自己。然后以前 \(j-1\) 个块新增一个所以插 \(j\) 个空。
    \(f[i][j]=f[i-1][j-1]\cdot j\)
  3. 合并原来两个块。
    这个也显然可以因为以前插入的块的元素都小于 \(i\) 所以自然符合规定。具体实现类似上文只不过不能插两端。
    \(f[i][j]=f[i-1][j+1]\cdot j\)

之后特判一下 \(s,t\) 即可,也就是说另起一段还是插在原来已经 \(j\) 个段的头里。然后就是如果已经有 \(s\) 就不能新开在最左边,如果有 \(t\) 就不能开在最右边。

代码

#include <bits/stdc++.h>
#define endl "\n"
using namespace std;
typedef long long ll;
const ll MAXN=2e3+5;
const ll MOD=1e9+7;
ll n,s,t;
ll dp[MAXN][MAXN];
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>s>>t;
    dp[1][1]=1;
    for(int i=2;i<=n;++i){
        for(int j=1;j<=i;++j){
            if(i==s||i==t){
                dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
                dp[i][j]%=MOD;
                continue;
            }   
            dp[i][j]+=(j-(i>s)-(i>t))*dp[i-1][j-1]%MOD+j*dp[i-1][j+1]%MOD;
            dp[i][j]%=MOD;
        }
    }
    cout<<dp[n][1]<<endl;
    return 0;
}
posted @ 2024-05-19 18:07  tanghg  阅读(8)  评论(0编辑  收藏  举报