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

Posted zcr-blog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[SDOI2016]排列计数(组合计数)相关的知识,希望对你有一定的参考价值。

题意:

求有多少种 $1$ 到 $n$ 的排列 $a$,满足序列恰好有 $m$ 个位置 $i$,使得 $a_i = i$

题解:

这就是个普通的部分错排问题。

因为要选 $m$ 个位置不是错排,共有$C_n^m$中选择。剩下的必须是完全错排。设$n$个数完全错排的方案数是$f_n$。则答案为$C_n^m imes f_{n-m}$。

怎么求f?

其实有一个递推式$f_i=(i-1) imes (f_{i-1}+f_{i-2})$。

考虑把第i个数放在前i-1个位置中的任意一个位置k,共有i-1中放法。

原本在第k个位置的数如果到了i,则剩下的i-2个数构成一个错排问题,乘上$f_{i-2}$即可。第k个位置的数不到i,则剩下i-1个数构成一个错排问题乘上$f_{i-1}$。

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll MOD = 1e9 + 7;
template <typename T> void read(T &x) {
    T ff = 1; char ch = getchar(); x = 0;
    for (; !isdigit(ch); ch = getchar()) if (ch == -) ff = -1;
    for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - 0;
    x *= ff;
}
template <typename T> void write(T x) {
    if (x > 9) write(x / 10);
    putchar(x % 10 + 0);
}
template <typename T> void print(T x) {
    if (x < 0) x = -x, putchar(-);
    write(x);
    putchar(
);
}
ll ksm(ll a, ll b, ll m) {
    ll ret = 1;
    while (b) {
        if (b & 1) ret = (ret * a) % m;
        a = (a * a) % m;
        b >>= 1;
    }
    return ret;
}
ll f[1000010], g[1000010];
ll n, m, t;
ll C(ll i, ll j) {
    return (((g[i] * ksm(g[j], MOD - 2, MOD)) % MOD) * ksm(g[i - j], MOD - 2, MOD)) % MOD;
}
int main() {
    read(t);
    g[0] = 1; g[1] = 1; g[2] = 2; f[1] = 0; f[2] = 1;
    for (int i = 3; i <= 1000000; i++) {
        f[i] = ((i - 1) * (f[i - 1] + f[i - 2])) % MOD;
        g[i] = (g[i - 1] * i) % MOD;
    }
    while (t--) {
        read(n); read(m);
        if (n == m) print(1);
        else print((C(n, m) * f[n - m]) % MOD);
    }
    return 0;
}

 

以上是关于[SDOI2016]排列计数(组合计数)的主要内容,如果未能解决你的问题,请参考以下文章

bzoj-4517 4517: [Sdoi2016]排列计数(组合数学)

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

BZOJ4517[Sdoi2016]排列计数 组合数+错排

bzoj4517: [Sdoi2016]排列计数

[SDOI2016]排列计数

[SDOI2016]排列计数