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

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

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值