题目
题解
先预处理出来每种大小的数的个数,并在这个过程进行判断是否连续(不大于 1),然后,我们可以从小到大进行插空法插数,那么如何插呢?假如,此时我们已经查到数 i,那么合法的插孔分为两种,第一种是插在两个 之间,另一种就是当首尾有 时,我们可以在首尾两侧进行插空。那么我们需要考虑的也就是此时考虑到了第 种数、有多少个相邻的第 种数对儿、首尾有几个第 种数,自然最后这个状态只有三种,就是 0、1、2,所以我们设 表示此时考虑到了第 种数时,首尾第 种数的个数分别为 0、1、2,相邻的第 种数对儿个数加上首尾第 种数的个数的和为 时的方案数。初始化,我们应该设 ,至于为什么,很容易理解,此时,第一种数的方案只有一种,首尾第一种数的个数为2,连续的第一种数对儿为 ,所以,你没有看错,
实际上就是此时插空时允许插空的数目。
综上所述,这个题核心就是插空法,组合数学的知识,另外还要使用一次插空法的地方是,要考虑将 个数划分为 份时。
就这样,没有什么问题了,需要讲解的都说的十分清楚了,如果还有什么问题,可以评论区留言哦~~~
哦,对了,最后结果自然是不同插空数时三种情况的和的和喽!也就是说需要控制该 dp
的第二维的量,因为我们说明了,第二维实际上就是此时允许的插空数目。Over~~~
#include<bits/stdc++.h>
using namespace std;
const int M=1e9+7,N=30002;
typedef long long ll;
int n,i,tot,a[N],j,k,cnt[102],x;
ll f[N][102][3],c[102][102],ans,tmp;
int main(){
scanf("%d",&n);
for (i=0;i<102;i++){
c[i][0]=1;
for (j=1;j<=i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%M;
}
scanf("%d",&a[1]);
cnt[tot=1]=1;
for (i=2;i<=n;i++){
scanf("%d",&a[i]);
if (a[i]-a[i-1]>1){
puts("0");
return 0;
}
if (a[i]==a[i-1]) cnt[tot]++;
else cnt[++tot]=1;
}
f[1][cnt[1]+1][2]=1;
for (i=1;i<tot;i++){
x=cnt[i+1];
for (j=0;j<=cnt[i]+1;j++)
for (k=1;k<=j;k++){
tmp=c[x-1][k-1];
if (x-k>=0) (f[i+1][x-k][0]+=(f[i][j][0]*c[j][k]+f[i][j][1]*c[j-1][k]+f[i][j][2]*c[j-2][k])%M*tmp%M)%=M;
if (x-k+1>=0) (f[i+1][x-k+1][1]+=(f[i][j][1]*c[j-1][k-1]+f[i][j][2]*c[j-2][k-1]*2)%M*tmp%M)%=M;
if (x-k+2>=0 && k-2>=0) (f[i+1][x-k+2][2]+=f[i][j][2]*c[j-2][k-2]%M*tmp%M)%=M;
}
}
for (i=0;i<=cnt[tot]+1;i++)
for (j=0;j<3;j++) (ans+=f[tot][i][j])%=M;
printf("%lld",ans);
}