Codeforces Round #175 DIV2 E Positions in Permutations

题目链接

题意:对于某个排列p1,p2,...pn,位置i为good position当且仅当| pi - i | = 1,给你一个N,表示排列的元素个数,求满足恰好有K个good  position的排列的个数,对结果Mod 1000000007。 

思路:DP。首先很容易就可以想到如下的状态dp,用dp[i][j] 表示前i个位置中正好有j个位置是good position的种数。那么对于第i个位置,它就可以分成两大种情况:

a、第i个位置不是good ; 这时候我们可以很容易地实现状态转移:dp[i][j] = dp[i-1][j] 。

b、第i个位置是good。 这种情况又可以分成两种小情况:

b1.i的取值(即pi的值)是i-1; 但是这里有会带来另外一个问题,那就是i-1是否可用的问题,换句话说就是i-2位置是否已经将i-1这个数已经用掉了,所以我们可以再加入一维状态s来表示i的最后三位的状态。

b2.i的取值是i+1 ,此时可以不受前面限制地进行转移。


最后我们可以由dp求出整个排列中只有i个元素是good的值,记为sum[i],sum[i] *= fac[N-i],那么sum[i]就是至少有i个元素是good的总数,这里还特别需要注意的一点就是sum[i] = a[i] + C[i+1][i]*a[i+1] + C[i+2][i]*a[i+2]....,所以我们最后可以反解出a[K]。

代码:

#include <stdio.h>
#include <string.h>
typedef long long LL ;
const int NN = 1010 ;
const LL Mod = 1000000007  ;
LL N , K ;
LL dp[NN][NN][2][2][2] ;
LL sum[NN] ;
LL fac[NN] ;
LL a[ NN ] ;
LL C[NN][NN] ;

void DP(){
    memset( dp, 0 ,sizeof(dp) ) ;

    dp[1][0][0][0][0] = 1 ;
    if( N > 1 )
        dp[1][1][0][0][1] = 1 ;
    for(int i=1;i<N;i++){
        for(int j=0;j<=i;j++){
            for(int a=0;a<2;a++){
                for(int b=0;b<2;b++){
                    for(int c=0;c<2;c++){
                        if( dp[i][j][a][b][c] == 0 )    continue ;
                        int tmp = dp[i][j][a][b][c] ;
                        dp[i+1][j][b][c][0] = ( dp[i+1][j][b][c][0] + tmp ) % Mod ;
                        if( b == 0 )   dp[i+1][j+1][b][c][0] = ( dp[i+1][j+1][b][c][0] + tmp ) % Mod ;
                        if( i+2<=N )   dp[i+1][j+1][b][c][1] = ( dp[i+1][j+1][b][c][1] + tmp ) % Mod ;
                    }
                }
            }
        }
    }
    for(int i=0;i<=N;i++){
        sum[i] = 0 ;
        sum[i] = dp[N][i][0][0][0] + dp[N][i][0][0][1] +
                dp[N][i][0][1][0] + dp[N][i][0][1][1] +
                dp[N][i][1][0][0] + dp[N][i][1][0][1] +
                dp[N][i][1][1][0] + dp[N][i][1][1][1] ;
        sum[i] %= Mod ;
        sum[i] = sum[i] * fac[N-i] % Mod ;
    }
}

int main(){
    fac[1] = fac[0] = 1 ;
    for(LL i=2;i<=1000;i++)    fac[i] = fac[i-1] * i % Mod ;
    C[0][0] = C[1][0] = C[1][1] = 1 ;
    for(int i=2;i<=1000;i++){
        for(int j=0;j<=i;j++){
            C[i][j] = C[i-1][j-1] ;
            if( i-1>=j )    C[i][j] = (C[i][j] + C[i-1][j]) % Mod ;
        }
    }
    while( scanf("%d %d",&N,&K) == 2 ){
        DP() ;
        a[N] = sum[N] ;
        for(int i=N-1;i>=K;i--){
            LL tmp = sum[i] ;
            for(int j=i+1;j<=N;j++){
                LL mid = C[j][i] * a[j] ;
                mid %= Mod ;
                tmp -= mid ;
                if( tmp < 0 )   tmp += Mod ;
            }
            a[i] = tmp ;
        }
        printf("%d\n",(int)a[K] );
    }
    return 0 ;
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值