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

先考虑怎样求出 1 n的所有排列中,错排(一个 1 n的排列 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值