#线性dp#洛谷 5999 [CEOI2016]kangaroo
题目
问有多少个长度为 \(n\) 的排列满足首项为 \(st\),末项为 \(ed\),
并且 \(\forall i\in (1,n),\left[a_{i-1}<a_i \oplus a_i<a_{i+1}\right]\),\(\oplus\) 表示异或
分析
这道题提供了一个求合法排列方案数的一种新方法。
设 \(dp[i][j]\) 表示前 \(i\) 个数,分成 \(j\) 段合法序列的方案数。
如果 \(i==st \or i==ed\) 那么要么新开一段或者直接并到左侧或右侧,也就是
\(dp[i][j]=dp[i-1][j-1]+dp[i-1][j]\)。
否则可以将这个当前最大数插入在两段之间,或者新开一段,但是首尾可能放不了,要减掉,也就是
\(dp[i][j]=dp[i-1][j+1]*j+dp[i-1][j-1]*(j-(i>st)-(i>ed))\)
初值 \(dp[1][1]=1\),答案为 \(dp[n][1]\)
代码
#include <cstdio>
using namespace std;
const int N=2011,mod=1000000007; int n,st,ed,dp[N][N];
int mo(int x,int y){return x+y>=mod?x+y-mod:x+y;}
int main(){
scanf("%d%d%d",&n,&st,&ed);
dp[1][1]=1;
for (int i=2;i<=n;++i)
if (i==st||i==ed){
for (int j=1;j<=i;++j)
dp[i][j]=mo(dp[i-1][j-1],dp[i-1][j]);
}else{
int now=(i>st)+(i>ed);
for (int j=1;j<=i;++j)
dp[i][j]=mo(1ll*dp[i-1][j-1]*(j-now)%mod,1ll*dp[i-1][j+1]*j%mod);
}
return !printf("%d",dp[n][1]);
}