感觉我的做法很复杂,但是我真的没有其他思路了。。。
题目说要有m个数在原位,其余m-n个都不在
那么这个答案就是组合问题与错排问题的乘积了。
现在我们考虑错排问题,很明显这个问题需要用到容斥原理
假设我们需要求n的错排个数,则有:
$$
P_n-C_n^1 * P_{n-1}+C_n^2 * P_{n-2}-···+(-1)^n*C_n^n * P_0
$$
这条式子是什么意思呢?
$C_n^2 * P_{n-2}$的意思是先选2个数在原位上,其余的都随意排列。
但是这样子是会超时的。
所以我们要考虑化简式子。
$$
C_n^a * P_{n-a} = \dfrac{n!}{a! * (n-a)!} * (n-a)! = \dfrac{n!}{a!}
$$
接下来我们只需要把$n!$提取出来,然后再用数组s记录下$\dfrac{1}{1!} - \dfrac{1}{2!} + \dfrac{1}{3!}···$就可以了。
最后公式就可以变成:
$$
C_n^m * (P_{n-m}-(n-m)! * s[n-m])
$$
然后就可以O(1)计算出结果了
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
void read(int &x) {
x = 0; int f = 0; char s = getchar();
while (!isdigit(s)) f |= s=='-', s = getchar();
while ( isdigit(s)) x = x * 10 + s - 48, s = getchar();
x = f ? - x : x;
}
typedef long long LL;
const int N = 1e6 + 10;
const LL P = 1e9 + 7;
LL fc[N], inv[N], s[N];
LL power(LL a, LL b) {
LL ans = 1;
while (b) {
if (b & 1) ans = ans * a % P;
a = a * a % P; b >>= 1;
}
return ans;
}
LL C(int n, int m) { return fc[n] * inv[m] % P * inv[n - m] % P; }
LL p(int n) { return fc[n];}
//LL CP(int n, int a) { return fc[n] * inv[a] % P;} // C(n,a) * P(n - a) = n! / a!
int main() {
fc[0] = 1; inv[0] = 1;
for (int i = 1; i <= 1000000; i++)
fc[i] = fc[i - 1] * i % P, inv[i] = power(fc[i], P - 2);
for (int i = 1, j = 1; i <= 1000000; i++, j *= -1)
s[i] = s[i - 1] + 1ll * j * inv[i] % P, s[i] %= P;
int T; cin >> T;
while (T--) {
int n, m; read(n), read(m);
LL s1 = fc[n - m] * s[n - m] % P;
LL s2 = (p(n - m) - s1 + P) % P;
LL ans = C(n, m) * s2 % P;
printf("%lld\n", ans);
}
return 0;
}