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\) 段,满足每一段的合法性。且要求每一次操作任意一段均为合法。
所以考虑:
- 将 \(i\) 插入到原来的块里。可这样后续的插入会有一种方案插在 \(i\) 的一侧,这样这个就又大又小不符合题意了。
- 新增一个块。
这个显然可以,因为一个块里只有它自己。然后以前 \(j-1\) 个块新增一个所以插 \(j\) 个空。
\(f[i][j]=f[i-1][j-1]\cdot j\) - 合并原来两个块。
这个也显然可以因为以前插入的块的元素都小于 \(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;
}