FZU 2282 - Wand (组合数学+错排公式)

题意

n个人有n把魔杖, 求这n个人至少有k个人拿到属于自己的魔杖的排列方式种数

思路

组合数学 + 错排公式

完全错排公式 : D(n)=(n-1)*(D(n-1)+D(n-2))

题目意思就是n个数字里要求至少k个数字位置不变,其余进行错排的方案数,容易想到不变的数字从k枚举到n,每次取i(k <= i < n)个出来,对剩下的n-i个进行错排,即C(n, i) * d[n - i] (d[n - i]表示对n-i个数进行错排的方案数),然后将它们累加,但是这样做超时了。
考虑到n较大,k较小,可以反过来处理,总的方案数为n!,可以从总的方案数里减去不符合条件的情况,即不变的个数从0枚举到k-1

AC代码

#include <iostream>
#include <cstdio>

using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const int maxn = 1e4+5;
ll d[maxn];
ll A[maxn];
ll C[maxn][100+5];

void init(){
    d[0] = 1, d[1] = 0, d[2] = 1;
    for( int i = 3; i <= 1e4; i++ )  //错排公式
        d[i] = (i-1)*((d[i-2] + d[i-1])%mod) % mod;
    A[0] = 1, A[1] = 1;
    for( int i = 2; i <= 1e4; i++ )  //阶乘
        A[i] = A[i-1]*i % mod;
    C[1][0] = 1, C[1][1] = 1;
    for( int i = 2; i <= 1e4; i++ ){  //组合数
        C[i][0] = 1;
        for( int j = 1; j <= i && j <= 100; j++ )
            C[i][j] = ( C[i-1][j]+C[i-1][j-1] ) % mod;
    }
}

int main()
{
    int T, n, k;
    init();
    scanf("%d",&T);
    while(T--){
        scanf("%d%d", &n, &k);
        ll w = 0;
        for( int i = 0; i < k; i++ )
            w = ( w + C[n][i]*d[n-i]%mod ) % mod;
        w = (A[n]-w+mod)%mod;
        printf("%lld\n",w);
    }
    return 0;
}
posted @ 2018-04-10 11:59  JinxiSui  阅读(165)  评论(0编辑  收藏  举报