码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2020-06-16 23:53:19      阅读:101      评论:0      收藏:0      [点我收藏+]

标签:sdi   temp   选择   pac   div   方案   cst   怎么   math   

题意:

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

题解:

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

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

怎么求f?

其实有一个递推式$f_i=(i-1) \times (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(\n);
}
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]排列计数(组合计数)

标签:sdi   temp   选择   pac   div   方案   cst   怎么   math   

原文地址:https://www.cnblogs.com/zcr-blog/p/13149598.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!