「基础算法」第1章 递推算法课堂过关
A. 【例题1】错排问题
题意
求多少个n个数的排列A,满足对于任意的(i (1leq i leq n) A_i eq i),输入n,输出一个整数,表示答案
思路
设(f(n))表示n个数的合法方案排列个数
- 考虑第x个元素,把他放到k位置上,一共有 n-1种放法
- 考虑第k个元素,共两种可能:
- 把k放在位置n,则对于除k,n以外的其他元素,错排即可,共(f(n-2))种方案
- 不把k放在位置n,则对于n-1个元素的错排,有(f(n-1))种方法
故,有递推式:
[f(x)=
left{
egin{aligned}
(n-1)cdot (f(x - 1) + f(x - 2)) quad (xgeq3)\
1qquadqquadqquadqquadqquadqquadqquad(x=1)\
2qquadqquadqquadqquadqquadqquadqquad(x=2)
end{aligned}
ight.
]
代码
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
int n;
int ans;
int f(int x) {//数据异常水,记忆化都不用
if(x == 1) return 0;
if(x == 2) return 1;
return (x - 1) * (f(x - 1) + f(x - 2));
}
signed main() {
cin >> n;
cout << f(n);
cout << endl;
return 0;
}
/*
0
1
3
12
56
321
2175
17008
150504
1485465
*/
B. 【例题2】奇怪汉诺塔
题意
有A、B、C、D四座塔, 给定n个圆盘,一开始全部放在A塔,直接输出n=1~12时的所有答案,无输入
具体规则同普通汉诺塔
思路
回顾三座塔的情况:
将A塔的n-1个圆盘移至B塔,最后一个圆盘直接移至C塔,再将B塔的所有圆盘移到C塔
有递推式:
[d(x)=
left{
egin{aligned}
&1+2cdot d(x - 1) qquad(xgeq2)\
&1qquadqquadqquadqquad(x=1)
end{aligned}
ight.
]
当汉若塔数量为4座时:
考虑将A塔的j个盘子转移到B塔(移动步数为(f(j))),然后不对B塔进行操作,将剩下n-j个盘子转移到D塔,移动步数为(d(n-j)),最后将B塔的j个盘子转移到D塔,移动步数为(f(j)),我们直接枚举j,取最小值即可
递推式:
[f(x)=
left{
egin{aligned}
&min {{d(x-j)+2cdot f(j)}} qquad(xgeq2,jin[0,x])\
&1qquadqquadqquadqquadqquadqquad (x=1)
end{aligned}
ight.
]
代码
#include <iostream>
#include <cstdio>
#define min_(_ , __) (_ < __ ? _ : __)
using namespace std;
int d[20] , f[20];
int main() {
d[1] = 1;
for(int i = 1 ; i <= 12 ; i++)
d[i] = 2 * d[i - 1] + 1;
f[1] = 1;
for(int i = 2 ; i <= 12 ; i++) {
f[i] = (1 << 29);
for(int j = 0 ; j <= i ; j++)
f[i] = min_(f[i] , 2 * f[j] + d[i - j]);
}
for(int i = 1 ; i <= 12 ; i++)
cout << f[i] << endl;
return 0;
}
/*答案
1
3
5
9
13
17
25
33
41
49
65
81
*/
C. 【例题3】数的划分
题意
将整数n分成k份,每份不为空,求不同的划分方案数(数字相同而顺序不同的两个方案视为相同的方案)
例如:下面三种分法被认为是相同的:
1,1,5; 1,5,1; 1,1,5.
思路
设$ f(n,k)$表示将整数n分成k份的不重复方案数
当(n>k)时:
- k份中至少有一份为“1”:方案数为(f(n-1,k-1))
- k份中没有一份为“1” :方案数为(f(n-k,k))(在n-k被分成k份的基础上,每一份都加上“1”)
故有递推式:
[f(n,k)=
left{
egin{aligned}
&f(n-1,k-1)+f(i-k,k) qquad (x>k)\
&1qquadqquadqquadqquadqquadqquadqquad(n=k)\
&0qquadqquadqquadqquadqquadqquadqquad(n<k)
end{aligned}
ight.
]
代码
#include <iostream>
#include <cstdio>
using namespace std;
int n , k;
int f[1010][1010];
int main() {
cin >> n >> k;
for(int i = 1 ; i <= n ; i++)
for(int j = 1 ; j <= k ; j++) {
if(i == j) f[i][j] = 1;
else if(i < j) f[i][j] = 0;
else f[i][j] = f[i - 1][j - 1] + f[i - j][j];'
}
cout << f[n][k];
return 0;
}
D. 【例题4】传球游戏
题目
思路
设(f(i , j)) 表示经过j次传球后,球落在第i个同学手中的方案数(说明一下,我代码里面把游戏刚开始,球在小蛮手上的情况当做第1次传球,故有一丢丢不一样)
很显然,有:
[f(i,j)=f(i-1,j-1)+f(i+1,j-1)
]
其中,根据题目“n 个同学站成一个圆圈”的背景,“i+1” 、"i-1"均应该在([1,n])范围内,越界自行处理
代码
#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
int n , m;
ll f[3010][3];
int main() {
cin >> n >> m;
++m;
f[1][1] = 1;
for(int i = 2 ; i < m ; i++) {
for(int j = 1 ; j <= n ; j++){
f[j][i & 1] = 0;
f[j][i & 1] = f[j == 1 ? n : j - 1][(i - 1) & 1] + f[j == n ? 1 : j + 1][(i - 1) & 1];
}
}
cout << f[n][(m - 1) & 1] + f[2][(m - 1) & 1] << endl;
return 0;
}
E. 【例题5】平铺方案
题目
思路
想出并写出算法10min,写出高精度40min
设(f(x))表示铺成2*x的矩形的方案数
显然(f(1)=1,f(2)=3)
对于其他情况,我们考虑:
- 将1*2的矩形竖着拼到2*(n-1)的矩形上,方案数为f(x-1)
- 将两个1*2的矩形横着拼到2*(n-2)的矩形上,方案数为f(x-2)
- 将一个2*2的矩形横着拼到2*(n-2)的矩形上,方案数为f(x-2)
故递推式:
[f(x)=
left{
egin{aligned}
&2cdot f(x - 2) + f(x - 1) quad(xgeq3)\
&1qquadqquadqquadqquadqquad(x=1)\
&3qquadqquadqquadqquadqquad(x=2)
end{aligned}
ight.
]
最后,看样例都知道要高精度
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
#define m 1010
struct bignum {
int a[m];
int siz;
void init() {
memset(a , 0 , sizeof(a));
this->siz = 0;
}
void print() {
// cout << siz << endl;
for(int i = 0 ; i < siz ; i++)
putchar(a[siz - i - 1] + '0');
putchar('
');
}
bignum operator + (const bignum &b) const{
bignum ans;
ans.init();
int g = 0;
ans.siz = (siz > b.siz ? siz : b.siz) - 1;
for(int i = 0 ; i <= ans.siz || g != 0 ; i++) {
if(i > ans.siz)
ans.siz = i;
ans.a[i] = g + a[i] + b.a[i];
g = ans.a[i] / 10;
ans.a[i] %= 10;
}
++ans.siz;
if(ans.a[ans.siz - 1] == 0)--ans.siz;
return ans;
}
};
bignum f[1010];
int main() {
int n;
int maxn = 3;
f[1].siz = 1 , f[1].a[0] = 1;
f[2].siz = 1 , f[2].a[0] = 3;
while(cin >> n) {
for(; maxn <= n ; maxn++)
f[maxn] = f[maxn - 1] + f[maxn - 2] + f[maxn - 2];
f[n].print();
if(n > maxn) maxn = n;
}
return 0;
}