数论学习之错排公式

先上题目: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)

二项式反演1

这个时候就用到神奇的构造了:

我们想求一个难求的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;
    }
}





猜你喜欢

转载自blog.csdn.net/neuq_zsmj/article/details/80332012
今日推荐