[BZOJ4517][SDOI2016]排列计数(排列组合)

来源:互联网 发布:淘宝快捷支付限额 编辑:程序博客网 时间:2024/05/16 14:08

先考虑怎样求出1n的所有排列中,错排(一个1n的排列A[1],A[2],...,A[n]的每一个值都有iA[i])的个数Dn
先考虑Dn的容斥求法:Dn=AnnAn1n+An2nAn3n+...+(1)nA0n
A化为阶乘形式后,利用分配律可以得到D的递推式:
1、D0=1
2、当i是奇数时,Dn=Dn1n1
3、当i是偶数时,Dn=Dn1n+1
回到原问题。可以发现,如果固定了满足i=A[i]m个位置,那么剩下的nm个数必将组成一个nm个数的错排。
所以答案=CmnDnm。预处理阶乘,阶乘逆元和D之后就可以O(1)求解一组数据了。
代码:

#include <cmath>#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;inline int read() {    int res = 0; bool bo = 0; char c;    while (((c = getchar()) < '0' || c > '9') && c != '-');    if (c == '-') bo = 1; else res = c - 48;    while ((c = getchar()) >= '0' && c <= '9')        res = (res << 3) + (res << 1) + (c - 48);    return bo ? ~res + 1 : res;}const int MaxN = 1e6, N = MaxN + 5, PYZ = 1e9 + 7;int n, m, A[N], D[N], inv[N];int qpow(int a, int b) {    int res = 1;    while (b) {        if (b & 1) res = 1ll * res * a % PYZ;        a = 1ll * a * a % PYZ;        b >>= 1;    }    return res;}int C(int n, int m) {    return 1ll * A[n] * inv[m] % PYZ * inv[n - m] % PYZ;}void init() {    int i; A[0] = D[0] = 1; for (i = 1; i <= MaxN; i++) {        A[i] = 1ll * A[i - 1] * i % PYZ;        D[i] = (1ll * D[i - 1] * i + (i & 1 ? -1 : 1)) % PYZ;        if (D[i] < 0) D[i] += PYZ;    }    inv[MaxN] = qpow(A[MaxN], PYZ - 2);    for (i = MaxN - 1; i >= 0; i--)        inv[i] = 1ll * inv[i + 1] * (i + 1) % PYZ;}void work() {    n = read(); m = read();    printf("%d\n", 1ll * C(n, m) * D[n - m] % PYZ);}int main() {    init(); int T = read();    while (T--) work();    return 0;}
原创粉丝点击