传送门
其实标签只是搞笑的。
没那么难。
二项式反演只是杀鸡用牛刀而已。
这道题也只是让你
n
≤
20
n\le20
n≤20的错排数而已。
还记得那个
O
(
n
)
O(n)
O(n)的递推式吗?
没错那个方法比我今天用的要快一些。
言归正传。
回忆一下二项式反演的式子:
f
n
=
∑
i
=
0
n
(
n
i
)
g
i
f_n=\sum_{i=0}^n\binom{n}{i}g_i
fn=∑i=0n(in)gi
=>
g
n
=
∑
i
=
0
n
(
(
−
1
)
i
(
n
n
−
i
)
f
i
)
g_n=\sum_{i=0}^n((-1)^i\binom{n}{n-i}f_i)
gn=∑i=0n((−1)i(n−in)fi)
证明很简单。
只用把第一个式子成立的条件带到第二个等式的右边就可以了。
然后这道题怎么用呢?
我们令
f
i
f_i
fi表示
i
i
i张牌任意排列的总方案数。
g
i
g_i
gi表示
i
i
i张牌全部错排的方案数。
那么根据分类计数的原理显然有:
f
n
=
∑
i
=
0
n
g
i
=
n
!
f_n=\sum_{i=0}^ng_i=n!
fn=∑i=0ngi=n!
于是
g
n
=
∑
i
=
0
n
(
(
−
1
)
i
(
n
i
)
f
i
)
=
∑
i
=
0
n
(
(
−
1
)
i
n
!
(
n
−
i
)
!
)
g_n=\sum_{i=0}^n((-1)^i\binom{n}{i}f_i)=\sum_{i=0}^n((-1)^i\frac{n!}{(n-i)!})
gn=∑i=0n((−1)i(in)fi)=∑i=0n((−1)i(n−i)!n!)
做完了。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=21;
ll fac[N];
int n;
int main(){
fac[0]=1;
for(int i=1;i<=20;++i)fac[i]=fac[i-1]*i;
while(~scanf("%d",&n)){
ll ans=0,tmp=1;
for(int i=0;i<=n;++i,tmp*=-1)ans+=tmp*fac[n]/fac[i];
cout<<ans<<'\n';
}
return 0;
}