先上题目:http://acm.hdu.edu.cn/showproblem.php?pid=1465
典型的错排问题
设D(n)表示n个数错排的方法数
1.dp推导
假设第i个和第j个元素,将第i个元素放在了第j个元素的位置上
此时第j个元素的放置有两种方法
1.放在i的位置上 这时其余(n-1)个元素错排列,即D(n-1)
2.不放在i的位置上,这时其余(n-2)个元素错排列即D(n-2)
而且j有(n-1)种取法,得到D(n)的递推式 :
D(n)=(n-1)(D(n-1)+D(n-2))
2.容斥原理推导
首先,n个数全排列有n!种情况
然后,我们在n个元素中任选一个元素放对,保证至少有一个元素正确排列,剩下元素全排列,此时的方法数为C(n,1)*(n-1)!
要求全错排列,就要减去这种情况,但我们此时把同时至少两个数正确排列的情况多排除了一次,要补加上,这时我们把至少有三个数不错排的情况又多加上了一次……
最后得到D(n)的表达式:D(n)=n!-C(n,1)*(n-1)!+C(n,2)*(n-2)!......
对C(n,i)*(n-i)!展开得:n!/i!
提取公因式得到D(n)=n!(1-1/1!+1/2!-1/3!......)
以下是用容斥定理解决错排问题的代码:
#include <iostream>
typedef long long LL;
using namespace std;
int n, flag;
LL fac[25];
LL ans;
void init()
{
fac[0] = 1;
for(int i = 1; i <= 20; i ++) fac[i] = fac[i-1] * i;
}
int main()
{
init();
while(~scanf("%d", &n))
{
ans = fac[n];
flag = -1;//容斥的符号变化
for(int i = 1; i <= n; i ++)
{
ans += flag * fac[n] / fac[i];
flag = -flag;
}
cout<<ans<<endl;
}
}
3.二项式反演推导:
先上二项式反演的公式://括号的内容表示C(n,i)
这个时候就用到神奇的构造了:
我们想求一个难求的g(n)时,如果有一个好求的f(n)表达式如上图所示,就可以用f(n)推出g(n)的表达式
构造:
f(n)表示n个数的全排列,即 f(n)=n!
g(i)表示恰好有i个数错排,即g(n)就是我们要求的结果。
而:f(n)=C(n,0)*g(0)+C(n,1)*g(1)+...+C(n,i)*g(i)+...+C(n,n)*g(n)
由反演公式得:g(n)=∑(-1)^(n-i) * C(n,i) * i! (从0到n)
下面是由二项式反演推导出错排公式的代码
#include <iostream> typedef long long LL; using namespace std; int n, flag; LL fac[25]; LL ans; void init() { fac[0] = 1; for(int i = 1; i <= 20; i ++) fac[i] = fac[i-1] * i; } int main() { init(); while(cin>>n&&n!=0)//二项式反演 { LL ans=0; int flag=n&1?-1:1;//这里第一个flag变量的正负由n的奇偶决定 for(int i=0;i<=n;i++) { ans+=flag*fac[n]/fac[n-i]; flag=-flag; } cout<<ans<<endl; } }