卧薪尝胆,厚积薄发。
SDOI2016 排列计数
Date: Mon Sep 17 10:38:00 CST 2018 In Category: NoCategory

Description:

求有多少种长为 $n$ 的排列满足恰好有 $m$ 个 $a[i]=i$ 的数。
$1\le n\le 1000000,1\le t\le 500000$

Solution:

先选出 $m$ 个,有 $C_m^n$ 种选法,可以预处理阶乘及其逆元 $O(1)$ 计算,剩下的每个都不能在自己位置,相当于是一个错位排列,根据错位排列递推式 $d[i]=(i-1)\times(d[i-1]+d[i-2])$ ,答案就是 $C_m^n\times d[n-m]$ 。关于错位排列递推式:可以这样理解,对于每一个位置 $k$ ,如果 $n$ 在 $k$ , $k$ 在 $n$ ,那剩下的就是一个错排,方案为 $d[i-2]$ ,如果 $k$ 不在 $n$ ,那么可以把第 $n$ 位看成第 $k$ 位,是一个 $i-1$ 的错排,这样的 $k$ 有 $i-1$ 种取值。

Code:


#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define MOD 1000000007
#define MAXN 1000001
typedef long long ll;
ll d[MAXN];
ll n,m;
ll fac[MAXN],inv[MAXN];
ll power(ll a,ll b)
{
ll res = 1;
while(b > 0)
{
if(b & 1)res = res * a % MOD;
a = a * a % MOD;
b = b >> 1;
}
return res;
}
ll C(ll n,ll m)
{
return fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
int main()
{
d[0] = 1;d[1] = 0;d[2] = 1;
for(ll i = 3;i < MAXN;++i)d[i] = (i - 1) * (d[i - 1] + d[i - 2]) % MOD;
fac[0] = 1;
for(ll i = 1;i < MAXN;++i)fac[i] = fac[i - 1] * i % MOD;
inv[MAXN - 1] = power(fac[MAXN - 1],MOD - 2);
for(int i = MAXN - 2;i >= 0;--i)inv[i] = inv[i + 1] * (i + 1) % MOD;
int testcases;
scanf("%d",&testcases);
for(int i = 1;i <= testcases;++i)
{
scanf("%lld%lld",&n,&m);
cout << C(n,m) * d[n - m] % MOD << endl;
}
return 0;
}
Copyright © 2020 wjh15101051
ღゝ◡╹)ノ♡