从开始看这题到现在,已经过了30多把lol的时间了。
话说今天又有一道排列计数的题让我懵逼。
题面
题意:问有多少长为n的排列a,恰好有m个位置存在a[i]=i。
我们枚举这n-m个a[i]≠i位置,有
Cmn
种情况。
对于x个数的排列,不存在a[i]=i的方案数设为f[x]。经过简单的打表可以发现
f[i]=f[i−1]∗i+(−1)i
就可以水出这题了。
但是像千反田一样好奇的我非常好奇,学习了一下机巧的二项式反演。
我曾听说过广义容斥原理
讲的是对于一个含有n个条件的集合,全集为P。f[i]为恰好满足i个条件的方案数,w[S]为满足集合S中条件的方案数,g[S]为S的元素个数。公式为
f[r]=∑S⊆P(−1)g[S]−r∗Crg[S]∗w[S]
我们设
h[x]=∑g[S]=xw[S]
就有
f[r]=∑i=rn(−1)i−r∗Cri∗h[i]
再回想h的计算方法,有
h[r]=∑i=rnCrif[i]⇔f[r]=∑i=rn(−1)i−r∗Cri∗h[i]
变形一下(我并不懂怎么变形,但每条柿子我都会证明),有
h[r]=∑i=0rCirf[i]⇔f[r]=∑i=0r(−1)r−i∗Cir∗h[i]
再有
h[r]=∑i=0r(−1)iCirf[i]⇔f[r]=∑i=0r(−1)i∗Cir∗h[i]
证一下最后一条吧
∑i=0r(−1)iCirf[i]
=∑i=0r(−1)iCir∑j=0i(−1)j∗Cji∗h[j]
=∑i=0r∑j=0i(−1)i(−1)jCir∗Cji∗h[j]
=∑i=0r∑j=0i(−1)i−jCjr∗Ci−jr−j∗h[j]
=∑j=0rCjr∗h[j]∑i=jr(−1)i−j∗Ci−jr−j
由于对于杨辉三角的每一行,奇数项之和等于偶数项之和,故
=f[r]
有n个集合,假若设任意i个集合的并集大小都为f[i],任意i个集合的补集的并集的大小都为h[i](不知道这样的集合存不存在),就有
h[r]=∑i=0r(−1)iCirf[i]
f[r]=∑i=0r(−1)i∗Cir∗h[i]
对于错排,设f[i]为i个数错排的方案。
显然有
∑i=0nCinf[i]=n!
反演过来就有
f[n]=∑i=0n(−1)n−i∗Cin∗i!
=n!∑i=0n(−1)ii!
终于打完了,嘟嘟噜。
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
#define mmst(a, b) memset(a, b, sizeof(a))
#define mmcp(a, b) memcpy(a, b, sizeof(b))
typedef long long LL;
const int nn=1000000,N=1003000;
const int p=1e9+7;
int T,n,m;
LL jc[N],Ijc[N],I[N],f[N];
int main()
{
jc[0]=Ijc[0]=I[1]=1;
for(int i=2;i<=nn;i++)
I[i]=(p-p/i)*I[p%i]%p;
for(int i=1;i<=nn;i++)
jc[i]=jc[i-1]*i%p;
for(int i=1;i<=nn;i++)
Ijc[i]=Ijc[i-1]*I[i]%p;
f[0]=1;
f[1]=0;
for(int i=2;i<=nn;i++)
if(i&1)
f[i]=(f[i-1]*i-1+p)%p;
else
f[i]=(f[i-1]*i+1)%p;
cin>>T;
while(T--)
{
scanf("%d%d",&n,&m);
LL ans=jc[n]*Ijc[m]%p*Ijc[n-m]%p*f[n-m]%p;
printf("%lld\n",ans);
}
return 0;
}
还是纱雾的水题专场