时空限制 1000ms / 128MB
题目描述
求有多少种长度为 n 的序列 A,满足以下条件:
1 ~ n 这 n 个数在序列中各出现了一次
若第 i 个数 A[i] 的值为 i,则称 i 是稳定的。序列恰好有 m 个数是稳定的
满足条件的序列可能很多,序列数对 10^9+710
9
+7 取模。
输入格式:
第一行一个数 T,表示有 T 组数据。
接下来 T 行,每行两个整数 n、m。
输出格式:
输出 T 行,每行一个数,表示求出的序列数
题目分析
明明就是数学题为什么都说是DP
思路并不难
我们假设已经有m个数稳定
那么剩下n-m个数都必须不在自己位置上,显然就是错排D[n-m]
而确定一开始哪m个数是稳定的
显然有
C
n
m
C_n^m
Cnm种选法
根据乘法原理有
a
n
s
=
C
n
m
∗
D
[
n
−
m
]
ans=C_n^m*D[n-m]
ans=Cnm∗D[n−m]
考虑到n,m和模数都较大,所以可以先预处理阶乘计算组合数
关于错拍
设
D
[
n
]
D[n]
D[n]表示
1
1
1~
n
n
n的错排方案数
则其有递推式
D
[
n
]
=
(
n
−
1
)
∗
(
D
[
n
−
1
]
+
D
[
n
−
2
]
)
D[n]=(n-1)*(D[n-1]+D[n-2])
D[n]=(n−1)∗(D[n−1]+D[n−2])
证明
假如我们要错排
1
1
1~
n
n
n
我们先将1错排(即将1放到
2
2
2~
n
n
n中某个位置),共n-1种方案
假设上一步将1放在了第k位
下一步有两种情况
1.将k放到1号位,那么问题变为剩下n-2个数的错排方案数D[n-2]
2.k不放1号位,那么我们将1号位视为k号位,那么问题转化为剩下n-1个数的错排方案数D[n-1]
根据乘法与加法原理有 D [ n ] = ( n − 1 ) ∗ ( D [ n − 1 ] + D [ n − 2 ] ) D[n]=(n-1)*(D[n-1]+D[n-2]) D[n]=(n−1)∗(D[n−1]+D[n−2])
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;
lt read()
{
lt f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int mod=1e9+7;
const int maxn=1000010;
int t;
lt fac[maxn],D[maxn],inv[maxn];
lt qpow(lt a,lt k,lt p)
{
lt res=1;
while(k>0){
if(k&1) res=(res*a)%p;
a=(a*a)%p;
k>>=1;
}
return res;
}
lt C(lt n,lt m){ return fac[n]*inv[m]%mod*inv[n-m]%mod;}
int main()
{
t=read(); fac[0]=1;
for(int i=1;i<=maxn;++i)
{
fac[i]=fac[i-1]*i%mod;
inv[i]=qpow(fac[i],mod-2,mod);
}
D[0]=D[1]=0; D[2]=1;
for(int i=3;i<=maxn;++i)
D[i]=(i-1)*(D[i-2]+D[i-1])%mod;
while(t--)
{
lt n=read(),m=read();
if(n-m==1) printf("0\n");
else if(m==n) printf("1\n");
else if(m==0) printf("%lld\n",D[n]);
else printf("%lld\n",C(n,m)*D[n-m]%mod);
}
return 0;
}