传送门
其实标签只是搞笑的。
没那么难。
二项式反演只是杀鸡用牛刀而已。
这道题也只是让你n≤20n\le20n≤20的错排数而已。
还记得那个O(n)O(n)O(n)的递推式吗?
没错那个方法比我今天用的要快一些。
言归正传。
回忆一下二项式反演的式子:
fn=∑i=0n(ni)gif_n=\sum_{i=0}^n\binom{n}{i}g_ifn=∑i=0n(in)gi
=>gn=∑i=0n((−1)i(nn−i)fi)g_n=\sum_{i=0}^n((-1)^i\binom{n}{n-i}f_i)gn=∑i=0n((−1)i(n−in)fi)
证明很简单。
只用把第一个式子成立的条件带到第二个等式的右边就可以了。
然后这道题怎么用呢?
我们令fif_ifi表示iii张牌任意排列的总方案数。
gig_igi表示iii张牌全部错排的方案数。
那么根据分类计数的原理显然有:
fn=∑i=0ngi=n!f_n=\sum_{i=0}^ng_i=n!fn=∑i=0ngi=n!
于是gn=∑i=0n((−1)i(ni)fi)=∑i=0n((−1)in!(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;
}