【SDOI2016】排列计数
原题:
n,m<=1e6,多组数据测试,数据组数<=5e5
因为a是排列,所以若让ai=i那就让它别动
选m个别动,剩下的重排列,要求每个人都不能在自己的位置上,求方案数
这不就错位排序么
关于错位排序以前写了个详解,现在发现若序列递推也听简单的
令f(n)为长度为n的序列错位排序的方案数
欲从f(n-1)递推到f(n),考虑长度n-1时的某一个方案
方法一:直接从n-1个数里选一个,跟第n个数交换,易证这样是对的
方法二:如果n-1个数里恰好有一个数在自己的位置上,那么交换这个数和第n个数也能得到合法方案
如果n-1个数里有一个以上的数不在自己位置上,易证此时得不到合法方案
所以方法一+方法二能不重复不遗漏地得到n个数的方案数
那么容易得到f(n)=(n-1)*f(n-1)+(n-1)*f(n-2)=(n-1)*[f(n-1)+f(n-2)]
那么本题答案就是f(n-m)*c(n,m)
现在的问题是求组合数,因为数据组数很大,所以不能暴力求组合数
而n和m也很大,不能递推求组合数
怎么破?
有同学说了,卢卡斯
卢卡斯大概是可以的,但是还有更简单的办法
那就是直接预处理出1e5以内的阶乘,就能公式法求组合数了
注意:
0个数的错位排序方案数为1
表现在本题中,就是当n=m的时候,恰好有f(0)*c(n,m)=1个方案
如果不特判这个就一分都拿不到
代码:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 #define LL long long 5 const int mo=1000000007; 6 int n,m; 7 LL f[1100000]; 8 LL j[1100000]; 9 LL qcp(LL x,LL y){ 10 LL bwl=1,z=x; 11 for(;y;y>>=1){ 12 if(y&1) bwl=bwl*z%mo; 13 z=z*z%mo; 14 } 15 return bwl; 16 } 17 int main(){ 18 f[0]=1,f[1]=0,f[2]=1; 19 //注意f[0] 20 for(int i=3;i<=1000000;++i) f[i]=(i-1)*(f[i-1]+f[i-2])%mo; 21 j[0]=1; 22 for(int i=1;i<=1000000;++i) j[i]=j[i-1]*i%mo; 23 int T; cin>>T; 24 while(T --> 0){ 25 scanf("%d%d",&n,&m); 26 printf("%lld\n",f[n-m]*j[n]%mo*qcp(j[m],mo-2)%mo*qcp(j[n-m],mo-2)%mo); 27 } 28 return 0; 29 }