[BZOJ 4517][Sdoi2016]排列计数(组合数学/错排公式)

Description

求有多少种长度为 n 的序列 A,满足以下条件:
1 ~ n 这 n 个数在序列中各出现了一次
若第 i 个数 A[i] 的值为 i,则称 i 是稳定的。序列恰好有 m 个数是稳定的
满足条件的序列可能很多,序列数对 10^9+7 取模。

Solution

其实是一个裸的错排啊,用F[n]来表示n个元素的错排方案,F[n]=(n-1)*(F[n-1]+F[n-2])

感性地理解一下这个公式大概是这样的:

如果我们把第n个元素放在第k个位置(k有n-1种取值)

1.把第k个元素也放在第n个位置,则有F[n-2]种方案【把剩下的元素错排】

2.把第k个元素放在其他位置,则有F[n-1]种方案【也可以看做把1~n-1的元素错排,然后交换n和k】

理性的证明我也不会= =

于是这题的答案就是C(n,m)*F[n-m]

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define Mod 1000000007
#define MAXN 1000005
typedef long long LL;
using namespace std;
int T,n,m;
LL fac[MAXN],inv[MAXN],F[MAXN];
int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
void init()
{
    fac[0]=1,inv[1]=1;
    for(int i=1;i<MAXN;i++)
    fac[i]=(fac[i-1]*i)%Mod;
    for(int i=2;i<MAXN;i++)
    inv[i]=((Mod-Mod/i)*inv[Mod%i])%Mod;
    inv[0]=1;
    for(int i=1;i<MAXN;i++)
    inv[i]=(inv[i]*inv[i-1])%Mod;
    F[0]=1,F[1]=0;
    for(int i=2;i<MAXN;i++)
    F[i]=((i-1)*(F[i-1]+F[i-2])%Mod)%Mod;
}
LL C(LL x,LL y)
{
    if(x<y)return 0;
    return ((fac[x]*inv[y])%Mod*inv[x-y])%Mod;
}
int main()
{
    T=read(),init();
    while(T--)
    {
        n=read(),m=read();
        printf("%lld\n",(C(n,m)*F[n-m])%Mod);
    }
    return 0;
}

 

posted @ 2017-06-10 17:11  Zars19  阅读(358)  评论(0编辑  收藏  举报