#线性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]);
}
posted @ 2021-12-17 21:26  lemondinosaur  阅读(17)  评论(0编辑  收藏  举报