晚上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;
}