【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
",f[n-m]*j[n]%mo*qcp(j[m],mo-2)%mo*qcp(j[n-m],mo-2)%mo);
27     }
28     return 0;
29 }
View Code
原文地址:https://www.cnblogs.com/cdcq/p/12658008.html