A是个比较好的递归例题,BCD用递推写会比较方便,但是递归也能写,主要是教学一个递归的剪枝,还有递推(递归)关系式的推导。
一些相对复杂的递推关系式用递推就很难来实现了。
E的话是一个dfs的经典例题。
F的话是一个相对综合的基础dfs。
A题
题目来源UVA10696:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1637
题意为现在定义了一个函数f91(x),当x>100的时候,f91(x)=x-10,当x<=100的时候f91(x)=f91(f91(x+11))。
属于一道比较好的递归例题,在这里你并不需要真的理解f91(x)这个函数的规律是如何,只是关注不同层次之间的递归关系,以及递归的终止条件即可。
此处x<=100的时候f91(x)=f91(f91(x+11))便是递归层次之间的递归关系,自身不断调用自身;
而当x>100的时候,f91(x)=x-10就是递归的终止条件,此时返回一个具体的整数值,不再调用自身继续递归下去。
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<bitset>
#include<cstdio>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<fstream>
#include<iomanip>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=1e5+7;
int f91(int x)
{
return x>100?x-10:f91(f91(x+11));
}
int32_t main()
{
IOS;
int x;
while(cin>>x,x) cout<<"f91("<<x<<") = "<<f91(x)<<endl;
}
B题
题目来源HDU2044:http://acm.hdu.edu.cn/showproblem.php?pid=2044
中文题意就不翻译了。
这道题用递推做会比较方便,用递推做的前提是认识到这道题中的特殊关系。
由于我们只能爬向右侧相邻的蜂房,因此我们很容易注意到,我们下一步如果要走到蜂房x,那么我们就只能从x-1和x-2这两个蜂房走过来。也就是说从起点走到蜂房x的方案数,等于从起点分别走到蜂房x-1和x-2这两个蜂房的方案数的和。这个关系和斐波那契数列的关系如出一辙。
再思考下去的话,我们会发现,我们所需要关注的只是起点和终点的下标差值,这个差值决定了我们最后输出的结果是斐波那契数列中的第几项。
由此我们可以直接预处理出斐波那契数列,然后对于每组数据,直接根据起点终点的下标差值输出对应斐波那契数列的值即可。
ps:注意斐波那契数列大概在四十项左右就会爆掉Int上限,因此这里要用longlong
递推写法:
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<bitset>
#include<cstdio>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<fstream>
#include<iomanip>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
#define ll long long
vector<ll>num(100);
int main()
{
num[0]=1;num[1]=2;
for(int i=2;i<50;i++) num[i]=num[i-1]+num[i-2];//预处理出斐波那契数列
int x;
cin>>x;
while(x--)
{
int a,b;
cin>>a>>b;
cout<<num[b-a-1]<<endl;
}
}
当然这道题也是可以用递归来写的,但是如果直接递归写的话会tle超时。
原因的话在于这里的递归层次最多有50层,而每一层递归,计算cal(x)的时候,cal(x)=cal(x-1)+cal(x-2),会分化出两层递归。cal(7)会分化出cal(6)和cal(5),而cal(6)又会分化出cal(5)和cal(4),注意到此处cal(5)被计算了,递归50层的话这样的重复计算次数会非常庞大,最终导致超时。
这里的话可以用ans[i]记录,当我们走到蜂房i的时候有多少种路径情况,并且当ans[i]已经计算的时候,就不再调用递归去计算,直接返回之前已经计算出的ans[i]即可。使用这样记录来减少递归重复运算的方法,被称为对递归的剪枝。递归的层级其实可以被画成一颗递归树,而我们通过记录减少递归重复运算就等同于对这棵树的重复枝杈进行了修剪去除。
这道题的递归关系还不算非常复杂,因此使用递推会比较容易写,但是如果类似于A题的递归关系或者更复杂的一些关系,使用递推就很难实现了。
递归剪枝:
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<bitset>
#include<cstdio>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<fstream>
#include<iomanip>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
ll ans[50];
ll dfs(int b,int a)
{
ll ret=0;
if(ans[b]!=-1) return ans[b];//如果当前b值已经被计算过,不调用下面的递归部分,直接返回已计算值即可
if(b-1>=a) ret+=dfs(b-1,a);
if(b-2>=a) ret+=dfs(b-2,a);
return ans[b]=ret;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
int a,b;
scanf("%d%d",&a,&b);
for(int i=a;i<=b;i++) ans[i]=-1;//-1标记当前位置仍未被计算过
ans[a]=1;
printf("%lld\n",dfs(b,a));
}
}
C题
题目来源HDU2046:http://acm.hdu.edu.cn/showproblem.php?pid=2046
中文题意不再赘述。
这道题最后仍然是和B题一样,是一个斐波那契数列的结果,关键在于如何自己观察思考推导出这样的一个结论。
首先很容易得到,当只有一列的时候,骨牌只有一种摆法,当有两列的时候,骨牌有两种摆法。
再之后运用到的递推思想,我们需要推导n=x的方案,那么我们假设n<x的情况都已经被计算出,那么我们就相当于在最后加了一列未确定摆法的格子。
最后一列两种摆法:
1.竖着摆放一根骨牌的摆法,这种摆法的方案数就等同于n=x-1,也就是没有最后一列时候的方案数,n=x-1时候的方案数。
2.横着摆放两根骨牌的摆法,这种摆法会使用掉x-1和x两列,也就是没有最后两列时候的方案数,n=x-2时候的方案数。
由此我们就已经推得递推关系式,又是一个斐波那契数列。
ps:书上的知识点都是死的,同样都是斐波那契数列,同样是这样的递推方式,有没有一个自己观察和总结规律的能力是完全不同的。
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<bitset>
#include<cstdio>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<fstream>
#include<iomanip>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
ll ans[60];
int32_t main()
{
IOS;
ans[1]=1;ans[2]=2;
for(int i=3;i<=50;i++) ans[i]=ans[i-1]+ans[i-2];
int n;
while(cin>>n) cout<<ans[n]<<endl;
}
D题
题目来源HDU2049:http://acm.hdu.edu.cn/showproblem.php?pid=2049
中文题意不再赘述。
这道题的话相对上一题的难度跨度略大,难点在于递推关系式如何推导出来。
首先n个人中m个选错了新娘,我们必然要计算一个C(n,m)的组合数。
之后我们需要思考m个人全部选错了新娘有几种情况,用cas[m]来记录。
当m=1的时候,cas[1]很容易推出为0,
当m=2的时候,cas[2]很容易推出为1。
之后采用和上面题目一样的思考方式,我们求取m=x的方案数的时候,是已经在m<x的所有方案数都已经求得的情况下,我们需要用m<x已经计算好的部分来推得m=x的方案数。
加进来的第m个人必定要在前面的m-1个人中选一个人的新娘来错选,因此总的方案数肯定要乘以一个m-1。
然后我们可以按照被错选的那个新娘原配新郎的当前选择分为两种情况来讨论:
1.那个新郎选择了新加进来的第m个人的原配新娘,也就是这两个人相互选择了对方的新娘,此时的方案数即为m-2人全部选错的时候的方案数
2.那个新郎没有选择新加进来的第m个人的原配新娘,此时我们把第m个人的原配的新娘当做这个新郎的原配新娘,这样的话前面的m-1个人在这种情况下又转变为了m-1个人全部选错的时候的方案数。
因此最后的答案就是C(n,m)
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<bitset>
#include<cstdio>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<fstream>
#include<iomanip>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
ll cas[30];//cas[i]存储i个人全部选错有多少种情况
int32_t main()
{
IOS;
cas[1]=0;cas[2]=1;
for(int i=3;i<=20;i++) cas[i]=(cas[i-1]+cas[i-2])*(i-1);
int c,m,n;
cin>>c;
while(c--)
{
cin>>n>>m;
ll ans=cas[m];
if(m>n/2) m=n-m;//计算组合数(n,m),如果m比n的一半大则m直接去n-m方便计算,避免值过大爆掉longlong上限
//组合数有许多种优秀的求法,这里使用了这种比较通俗易懂的写法
for(int i=0;i<m;i++) ans*=n-i;
for(int i=1;i<=m;i++) ans/=i;
cout<<ans<<endl;
}
}
E题
题目来源HDU1241:http://acm.hdu.edu.cn/showproblem.php?pid=1241
vjudge上放的中文题意,不再赘述。
直接看代码注释即可。
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<bitset>
#include<cstdio>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<fstream>
#include<iomanip>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int n,m;
char field[107][107];
void dfs(int a,int b)//对当前这一块油田进行dfs,全部置为*地面
{
if(a<0||a>=m) return;
if(b<0||b>=n) return;//如果dfs到了矩形区域外则结束当前dfs
if(field[a][b]=='*') return;//如果dfs到了地面也结束当前dfs
field[a][b]='*';//将当前地块置为地面
for(int i=-1;i<=1;i++)
for(int j=-1;j<=1;j++)//向周围8个方向走
if(i!=0||j!=0) dfs(a+i,b+j);
}
int32_t main()
{
IOS;
while(cin>>m>>n,m)
{
for(int i=0;i<m;i++) cin>>field[i];
int ans=0;
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
if(field[i][j]=='@')
{
ans++;
dfs(i,j);
}
cout<<ans<<endl;
}
}
F题
题目来源HDU1016:http://acm.hdu.edu.cn/showproblem.php?pid=1016
vjudge上放了中文题意,不再赘述。
直接看代码注释即可,考察代码实现基本功,写法各有不同。
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<bitset>
#include<cstdio>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<fstream>
#include<iomanip>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int n;
vector<vector<int>>ans;
vector<int>now;
bool prime[40];
bool num[30];//num[i]存储i这个数字当前是否已经被使用过
void dfs()
{
if(now.size()==n)
{
if(prime[now[n-1]+now[0]]) ans.push_back(now);//如果当前已经构造出了n个数字,并且判断下头和尾加起来是否也是素数
//如果都满足的话,代表是一组答案,放入ans数组里
return ;//递归终止
}
for(int i=1;i<=n;i++)//枚举每一个数字
{
if(!num[i]&&prime[i+now[now.size()-1]])//如果当前数字未被使用并且和前一个数字加起来是素数则继续递归
{
now.push_back(i);
num[i]=1;
dfs();
num[i]=0;//还原num和now
now.pop_back();
}
}
}
int32_t main()
{
IOS;
for(int i=2;i<40;i++)//预处理出40以内哪些数字是素数
{
prime[i]=1;
for(int j=2;j*j<=i;j++)
if(i%j==0) prime[i]=0;
}
int cas=0;
memset(num,0,sizeof(num));
while(cin>>n)
{
ans.clear();
now.resize(1,1);num[1]=1;
dfs();
cout<<"Case "<<++cas<<':'<<endl;
for(int i=0;i<ans.size();i++)
{
for(int j=0;j<ans[i].size();j++)
{
if(j) cout<<' ';
cout<<ans[i][j];
}
cout<<endl;
}
cout<<endl;
}
}