Luogu P4071 [SDOI2016]排列计数

晚上XZTdalao给我推荐了这道数论题。太棒了又可以A一道省选题了

其实这道题也就考一个错排公式+组合数+乘法逆元

我们来一步一步分析

错排公式

通俗的说就是把n个1~n的数排成一个序列A,并使得所有的a[i]!=i(1<=i<=n)

对于这道题我们可以进行转化,要求m个a[i]==i的个数,我们可以把它转化成求(n-m)个a[i]!=i的个数

然后用到错排公式:

d[i]=(i-1)*(d[i-1]+d[i-2])

其中d[i]表示i个数的错排方案

组合数

这个就比较简单了,比较经典的公式

C(m,n)=n!/(m!*(n-m)!)

如果通过组合数的递推公式...等着T死吧

乘法逆元

这个就比较diao了,因为我们知道,模运算对于除法是不适用的

因为你一个很大的数模了以后就突然很小了

所以我们需要引进逆元的概念,你可以简单地把它理解成一个数在模一个数的意义下的倒数,即(x为逆元)

ax≡1(mod p)

但是,p一般都为质数,因此我们可以通过费马小定理来求逆元

费马小定理为:

a^(p-1)≡1(mod p)

所以,我们得到:

a*a^(p-2)≡1(mod p)

因此a的逆元就是a^(p-2)

快速幂求之

所以综合起来这道题就A了

CODE

#include<cstdio>
using namespace std;
typedef long long LL;
const LL N=1000005,mod=1e9+7;
LL d[N],fact[N],t,n,m;
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(LL &x)
{
    x=0; char ch=tc();
    while (ch<'0'||ch>'9') ch=tc();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=tc();
}
inline void write(LL x)
{
    if (x/10) write(x/10);
    putchar(x%10+'0');
}
inline LL quick_pow(LL x,LL p)
{
    LL res=1;
    while (p)
    {
        if (p&1) res=1ll*(res*x)%mod;
        x=1ll*(x*x)%mod;
        p>>=1;
    }
    return res;
}
inline LL C(LL m,LL n)
{
    return (((long long)(fact[n]*quick_pow(fact[m],mod-2))%mod)*quick_pow(fact[n-m],mod-2))%mod;
}
int main()
{
    register LL i;
    for (fact[0]=fact[1]=1,i=2;i<N;++i)
    fact[i]=(LL)(fact[i-1]*i)%mod;
    for (d[1]=0,d[0]=d[2]=1,i=3;i<N;++i)
    d[i]=(LL)(i-1)*(d[i-1]+d[i-2])%mod;
    read(t);
    while (t--)
    {
        read(n); read(m); m=n-m;
        write((d[m]*C(m,n))%mod); putchar('
');
    }
    return 0;
}
原文地址:https://www.cnblogs.com/cjjsb/p/8762719.html