递归入门——错排及其应用

一、常见递归

​ 简单题:母牛的故事骨牌铺方格一只小蜜蜂...

​ 中等题:不容易系列之(3)—— LELE的RPG难题阿牛的EOF牛肉串

​ 较难题:神、上帝以及老天爷不容易系列之(4)——考新郎

​ 变态题:折线分割平面

​ 简单题,显而易见的规律;中等题,略加思考推出来的规律;较难题,深度思考(其实是我高中数学弱,不会错排😢😢😢);变态题,我想不出来,看题解也没看懂的题。

二、递推公式:斐波那契型

\[F(n) = F(n-1) + F(n-2) \]

​ 上述题目除了折线分割平面之外,都是这个公式的变形。

较难题需要用到错排,今天细讲错排。

三、错排公式的推导和应用

错排的定义:一段序列中一共有n个元素,那么可知这些元素一共有n!种排列方法。假如在进行排列时,原来所有的元素都不在原来的位置,那么称这个排列为错排。而错排数所指的就是在一段有n个元素的序列中,有多少种排列方式是错排。

\[递归关系:D(n)=(n-1)(D(n-1)+D(n-2)) 特别地有D(1)=0,D(2)=1; \\错排公式:D(n)=(n!)[(-1)^0/0!+(-1)^1/(1!)+(-1)^2/(2!) +(-1)^3/(3!)+......+(-1)^n/(n!)]; \\其中n!=n*(n-1)*(n-2)*......3*2*1 特别地有0!=1\quad 1!=1 \]

递推思想:


一共分为两步

第一步:

​ 错排(不能选择自己本来就在的位置)第一个元素,在n个位置中任选一个位置,有 n-1 种选法。

第二步:

​ 错排其余n-1个元素,也是需要分情况和种类的。因为这需要看第一步的结果,如果第一个元素落在第k个位置上,第二步就需要把k号元素进行错排,k号元素错排位置的不同将导致不同的情况会发生:

.假设k号元素正好落在了第一个元素的位置,那么就可以将第一个元素和第k个元素完全剔除出去,因为相当于只是他们两者互换了位置,其他元素暂时还没有发生变动。留下来的n-2元素进行错排的话,那么我们就可以得到了D(n-2)种 的错排方式。

.若k号元素不排到第一个元素的位置,我们可以暂时将现在排在k号位置的第一个元素剔除出去,生下来的就只包含k号元素和原来n-2个的元素了。这时,我们可以将原来的第一个元素的位置看做是现在k号元素的原本位置,因为k号元素不能够放在原来的位置上,所以就相当于是原来的n-2个元素和k共计n-1个元素进行完全的错排。那么一共就有D(n-1)种方法。

综上所述:第二步得到D(n-1)+D(n-2)种方法,第一步是n-1种,由于是分步进行,所以结果为(n-1)*[D(n-1)+D(n-2)]种方法。

四、实战

神、上帝以及老天爷

​ 典型的错排题目神、上帝以及老天爷,这道题的题意就是求所有的组合情况中错排所占的比例,题目要求的就是这个。

\[\frac{(n-1)*[D(n-1)+D(n-2)]}{N!} \]

下面就是AC代码了:

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int t;
    int s;
    double a[20+5] = {0,0,1};	//错排的数组
    double b[20+5] = {2,2,2};	//全排的数组
    int i = 3;

    cin >> t;
    while(t--)
    {
        cin >> s;
        for( ; i <= s; i++)
        {
            a[i] = (i-1)*(a[i-1]+a[i-2]);	//错排
            b[i] = b[i-1]*i;				//全排
        }
        cout << fixed << setprecision(2) << a[s]/b[s]*100 << "%" << endl;
    }
}

不容易系列之(4)——考新郎

​ 这道题的题意为在n对新人里面挑出m对新人来错排,那么实际让我们求得就是挑出m对新人的方法乘以m对新人的错排方法。就是下面这个公式。

\[C_n ^m*(n-1)*[D(n-1)+D(n-2)] \]

下面是AC代码:

#include <bits/stdc++.h>

typedef long long ll;

using namespace std;

ll factorial(int n, int m)	//求组合数
{
	ll ans = 1;
	if(m < n-m)
        m = n-m;
	for(int i = m+1; i <= n; i++)
        ans *= i;
	for(int j = 1; j <= n - m; j++)
        ans /= j;
	return ans;
}

int main()
{
    int t;
    int n, m;
    ll a[20+5] = {0,0,1};
    int i = 3;
    int temp;

    cin >> t;
    while(t--)
    {
        cin >> n >> m;
        if( n < m)
        {
            temp = n;
            n = m;
            m = temp;
        }
        for( ; i <= m; i++)		//错排
            a[i] = (i-1)*(a[i-1]+a[i-2]);
        cout << factorial(n,m)*a[m] << endl;//输出错排*组合数
    }
}

求组合数代码(用阶乘不到20就超出long long的范围了):

typedef long long ll;
ll factorial(int n, int m)	//求组合数
{
	ll ans = 1;
	if(m < n-m)
        m = n-m;
	for(int i = m+1; i <= n; i++)
        ans *= i;
	for(int j = 1; j <= n - m; j++)
        ans /= j;
	return ans;
}

posted @ 2018-12-12 14:56  Trirabbits三兔  阅读(561)  评论(0编辑  收藏  举报