Description
求有多少种长度为 n 的序列 A,满足以下条件:
1 ~ n 这 n 个数在序列中各出现了一次
若第 i 个数 A[i] 的值为 i,则称 i 是稳定的。序列恰好有 m 个数是稳定的
满足条件的序列可能很多,序列数对 10^9+7 取模。
Input
第一行一个数 T,表示有 T 组数据。
接下来 T 行,每行两个整数 n、m。
T=500000,n≤1000000,m≤1000000
Output
输出 T 行,每行一个数,表示求出的序列数
Sample Input
5
1 0
1 1
5 2
100 50
10000 5000
Sample Output
0
1
20
578028887
60695423
解析:
感觉好简单的签到题。
一看题目就是一个组合数学,并且要求
O
(
1
)
O(1)
O(1)完成询问。
好吧我还以为很难。。。然后5分钟写完代码。。。
由于数据范围很大,显然需要我们预处理阶乘以及阶乘逆元,那就递推一下吧。
然后我们再看,要求恰好有 m m m个数是在正确的位置上。那么我们选取这 m m m个数的方案数就是 ( m n ) (^n_m) (mn)。那么我们再处理一个错排方案数。递推式如下 d 0 = d 1 = 0 , d 2 = 1 d_0=d_1=0,d_2=1 d0=d1=0,d2=1 d n = ( n − 1 ) ∗ ( d n − 1 + d n − 2 ) d_n=(n-1)*(d_{n-1}+d_{n-2}) dn=(n−1)∗(dn−1+dn−2)
网上关于错排问题的详解已经很多了,这里就不具体阐述。
然后就是细节。
显然阶乘求组合数的细节想必不用我多说,主要这里的错排。
当 m < n m<n m<n时候,一切都好说。
但是错排问题中
d
0
=
0
d_0=0
d0=0,但是这里当
n
=
=
m
n==m
n==m的时候显然有一种排列方式满足题意。而
d
0
d_0
d0在递推中又不会用到,那么就直接初始化为
1
1
1。
然后发现错排的递推能够和其他递推一起写了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
cs ll mod=1000000007;
inline
ll getint(){
re ll num=0;
re char c;
while(!isdigit(c=gc()));
while(isdigit(c))num=(num<<1)+(num<<3)+(c^48),c=gc();
return num;
}
inline
void outint(ll a){
static char ch[23];
if(a==0)pc('0');
while(a)ch[++ch[0]]=(a-a/10*10)^48,a/=10;
while(ch[0])pc(ch[ch[0]--]);
}
ll d[1000001]={1,0},fac[1000001]={1,1},inv[1000001]={1,1},ifac[1000001]={1,1};
inline
ll c(int n,int m){
if(m>n)return 0;
return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int T;
int main(){
for(int re i=2;i<=1000000;++i)
fac[i]=fac[i-1]*i%mod,
inv[i]=(mod-mod/i)*inv[mod%i]%mod,
ifac[i]=ifac[i-1]*inv[i]%mod,
d[i]=(d[i-1]+d[i-2])%mod*(i-1)%mod;
T=getint();
while(T--){
int n=getint(),m=getint();
outint(d[n-m]*c(n,m)%mod);
pc('\n');
}
return 0;
}