BZOJ4517 [Sdoi2016]排列计数 【组合数 + dp】
题目
求有多少种长度为 n 的序列 A,满足以下条件:
1 ~ n 这 n 个数在序列中各出现了一次
若第 i 个数 A[i] 的值为 i,则称 i 是稳定的。序列恰好有 m 个数是稳定的
满足条件的序列可能很多,序列数对 10^9+7 取模。
输入格式
第一行一个数 T,表示有 T 组数据。
接下来 T 行,每行两个整数 n、m。
T=500000,n≤1000000,m≤1000000
输出格式
输出 T 行,每行一个数,表示求出的序列数
输入样例
5
1 0
1 1
5 2
100 50
10000 5000
输出样例
0
1
20
578028887
60695423
题解
考虑哪些位置是稳定的,剩余位置就不能稳定
我们设\(f[i]\)表示\(i\)个数都不稳定的方案数
那么\(ans = C_{n}^{m} * f[n - m]\)
我们预处理阶乘和\(f[i]\)就可以了
对于\(f[i]\),我们考虑第\(i\)个数放哪,显然有\(i - 1\)个位置可以放
放完后剩余\(i - 1\)个数和\(i - 1\)个位置,其中有一个位置都可以放,有一个数的位置被占了,所以可以任意放
那我们设\(g[i]\)表示这种情况下的方案数,如果我们称那个可以任意放的数为自由元,那个位置为自由位置
那么我们考虑自由元放在什么位置
①如果放在自由位置,那么剩余的数成了\(f[i - 1]\)
②如果不放在自由位置,有\(i - 1\)个位置,剩余\(i - 1\)的方案就是\(g[i - 1]\)
综上
\[f[i] = (i - 1) * g[i - 1]
\]
\[g[i] = f[i - 1] + (i - 1) * g[i - 1]
\]
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long int
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<' '; puts("");
using namespace std;
const int maxn = 1000005,maxm = 100005,INF = 1000000000,P = 1000000007;
inline int read(){
int out = 0,flag = 1; char c = getchar();
while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
return out * flag;
}
LL fac[maxn],inv[maxn],fv[maxn],f[maxn],g[maxn];
int n,m,T;
LL C(int x,int y){
return fac[x] * fv[y] % P * fv[x - y] % P;
}
int main(){
fac[0] = 1;
for (int i = 1; i <= 1000000; i++) fac[i] = fac[i - 1] * i % P;
inv[0] = inv[1] = 1;
for (int i = 2; i <= 1000000; i++) inv[i] = (P - P / i) * inv[P % i] % P;
fv[0] = 1;
for (int i = 1; i <= 1000000; i++) fv[i] = fv[i - 1] * inv[i] % P;
f[0] = 1; f[1] = 0; g[1] = 1; g[0] = 1;
for (int i = 2; i <= 1000000; i++){
f[i] = (i - 1) * g[i - 1] % P;
g[i] = (f[i - 1] + (i - 1) * g[i - 1] % P) % P;
}
T = read();
while (T--){
n = read(); m = read();
printf("%lld\n",C(n,m) * f[n - m] % P);
}
return 0;
}