一、常见递归
中等题:不容易系列之(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;
}