【BZOJ4517】排列计数(SDOI2016)-组合数学:错排

测试地址:排列计数
做法:本题需要用到组合数学中的错排问题。
首先,如果两个序列中稳定的位置不同,那么两个序列肯定不同,因此我们枚举稳定的位置,有Cnm种方案,然后对于剩下的元素,它们不能处在编号和它相同的位置上,学过组合数学的同学应该知道这就是经典的错排问题,令长为n的排列中,对所有1in都有aii的排列数为D(n)个,我们有递推式:
D(n)=(n1)(D(n1)+D(n2))
边界条件为D(0)=1,D(1)=0。那么本题的答案就是CnmD(nm)
如果只是摆递推式就太没意思了,就来讲讲这个递推式是怎么来的吧。
我们枚举1所在的位置,显然它不可能在1号位置,那么其它任意位置它都可以选,共n1种选择。然后对于它选择的位置x,我们讨论x在不在位置1上:
如果x在位置1上,那么其它n2个元素都不能在自己的位置上,方案数为D(n2)
如果x不在位置1上,那么它不能在位置1,而其它n2个元素都不能在自己的位置上,方案数为D(n1)
这就是上述递推式的由来。于是我们就解决了这一题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int T,n[500010],m[500010],maxn=0;
ll fac[1000010],inv[1000010],fi[1000010],D[1000010];

ll C(int n,int m)
{
    return fac[n]*fi[m]%mod*fi[n-m]%mod;
}

int main()
{
    scanf("%d",&T);
    for(int i=1;i<=T;i++)
    {
        scanf("%d%d",&n[i],&m[i]);
        maxn=max(maxn,n[i]);
    }

    fac[0]=fac[1]=inv[0]=inv[1]=fi[0]=fi[1]=1;
    D[0]=1,D[1]=0;
    for(ll i=2;i<=(ll)maxn;i++)
    {
        fac[i]=fac[i-1]*i%mod;
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
        fi[i]=fi[i-1]*inv[i]%mod;
        D[i]=(i-1)*(D[i-1]+D[i-2])%mod;
    }

    for(int i=1;i<=T;i++)
        printf("%lld\n",C(n[i],m[i])*D[n[i]-m[i]]%mod);

    return 0;
}
posted @ 2018-04-17 16:10  Maxwei_wzj  阅读(120)  评论(0编辑  收藏  举报