[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]排列计数(组合计数)的主要内容,如果未能解决你的问题,请参考以下文章