51nod 1780 完美序列

题目
题解
先预处理出来每种大小的数的个数,并在这个过程进行判断是否连续(不大于 1),然后,我们可以从小到大进行插空法插数,那么如何插呢?假如,此时我们已经查到数 i,那么合法的插孔分为两种,第一种是插在两个 i 1 之间,另一种就是当首尾有 i 1 时,我们可以在首尾两侧进行插空。那么我们需要考虑的也就是此时考虑到了第 i 种数、有多少个相邻的第 i 种数对儿、首尾有几个第 i 种数,自然最后这个状态只有三种,就是 0、1、2,所以我们设 d p [ i ] [ j ] [ 0 / 1 / 2 ] 表示此时考虑到了第 i 种数时,首尾第 i 种数的个数分别为 0、1、2,相邻的第 i 种数对儿个数加上首尾第 i 种数的个数的和为 j 时的方案数。初始化,我们应该设 d p [ 1 ] [ c n t [ 1 ] + 1 ] [ 2 ] = 1 ,至于为什么,很容易理解,此时,第一种数的方案只有一种,首尾第一种数的个数为2,连续的第一种数对儿为 c n t [ 1 ] 1 ,所以,你没有看错, c n t [ 1 ] + 1
实际上就是此时插空时允许插空的数目。
综上所述,这个题核心就是插空法,组合数学的知识,另外还要使用一次插空法的地方是,要考虑将 c n t [ i ] 个数划分为 k 份时。
就这样,没有什么问题了,需要讲解的都说的十分清楚了,如果还有什么问题,可以评论区留言哦~~~

哦,对了,最后结果自然是不同插空数时三种情况的和的和喽!也就是说需要控制该 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);
}

猜你喜欢

转载自blog.csdn.net/xumingyang0/article/details/80969332