首先,第一题是超级台阶问题。
对于这样的问题,调用递归函数进行一遍遍的运算其实是很麻烦的,这个时候就需要进行函数初始化了,用一个空数组记录每次下标对应的结果,如果这个下标没有出现过,就对这个下标进行赋值,如果说出现过,就直接输出。
但是应该怎么输出?
应该回溯着输出,这样才能得到正确的结果。
比如说A https://vjudge.net/contest/273364#status//A/0/ 超级台阶问题
我们知道当有级台阶的时候,上楼的方案只有一种,这个时候可以把这个条件作为初始值,用a[]=1,a[]=1来表示,以后在判断的过程中,但凡发现a[]=1就输出这个值到递归的上一级。
AC代码如下:
#include<stdio.h>
#include<algorithm>
using namespace std;
int T,n;
int a[50];
int dfs(int n)
{
if(a[n]) return a[n];
else
return a[n]=dfs(n-1)+dfs(n-2);
}
int main()
{
scanf("%d",&T);
while(T--)
{
a[1]=1;
a[2]=1;
a[3]=2;
scanf("%d",&n);//1到40
printf("%d\n",dfs(n));
}
return 0;
}
这个代码的关键点就是 a[n]=dfs(n-1)+dfs(n-2),这使得过程优化了很多很多。
接下来就是B题了。
https://vjudge.net/contest/273364#problem/B 汉诺塔问题。
解法的基本思想是递归。假设有A、B、C三个塔,A塔有N块盘,目标是把这些盘全部移到C塔。那么先把A塔顶部的N-1块盘移动到B塔,再把A塔剩下的大盘移到C,最后把B塔的N-1块盘移到C。 每次移动多于一块盘时,则再次使用上述算法来移动。
怎么在题目中说的大木块不能放在小木块的基础之上,把A塔上面的N-1个盘移动到B塔上面呢?
比如现在有三个木块,从小到大分为da zhong xiao,首先,把xiao移动到C,然后把zhong移动到b,然后把xiao移动到b,然后把da移动到c,然后把xiao移动到A,然后把zhong移动到C,然后把小移动到C
也就是说,把a上面的(n-1)个借助c移动到b,然后把a上面的第n个移动到c,然后把b上面的(n-1)个借助a移动到c
参考网站:
http://blog.csdn.net/geekwangminli/article/details/7981570
http://www.cnblogs.com/yanlingyin/archive/2011/11/14/2247594.html(推荐这个)
那么现在知道了怎么操作后,就是怎么写代码,怎么输出了。
这个是每一步的移动过程,
具体的实现就是
if(n==1)
{
printf("%d:%c->%c\n",n,a,c);
return ;
}
bfs(n-1,a,c,b);
printf("%d:%c->%c\n",n,a,c);
bfs(n-1,b,a,c);
不要管如何移动。
接下来就是C题了
https://vjudge.net/contest/273364#problem/C
每个人相邻两人身上数字之和都为素数时,便找到了一个环
那么可以在程序的开头写一个素数判断的函数,比如:
int isp[1000],flag[1000];
void is_prime()///素数打表,如果isp等于1就代表是素数
{
int k=0;
memset(isp,0,sizeof(isp));
memset(flag,0,sizeof(0));
for(int i=2; i<=7; i++)
if(!flag[i])
for(int j=i*i; j<=50; j+=i)
flag[j]=1;
for(int i=2; i<=50; i++)
if(!flag[i])
isp[i]=1;
}//素数打表
//这里有一个关键点就是在主函数外面,数组的初始值为0.
如果说isp【i】==1,那么,这个数就是素数
首先,记录1到index[1],然后判断2是否成立
1.如果成立的话,就把2记录到index[2],让后判断3是否成立
2.如果不成立的话,就过,判断3是否成立
想象一下,判断是否成立以后,数字不从头开始会怎么样?
如果说不从头开始,就会WRONG,如果说从开始的话,怎么从头开始呢?
这个时候就需要一个数组记录走过的数是否出现过了,(学长在讲的时候说这是初始化,表示,,,,,)
在我写的代码里面index,下面先弄出来我写的一个代码:
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxx=50;
int index[50];
int num;
int n;
int a[50];
int isp[100],flag[100];
void is_prime()///素数打表,如果isp等于1就代表是素数
{
int k=0;
memset(isp,0,sizeof(isp));
memset(flag,0,sizeof(0));
for(int i=2; i<=5; i++)
if(!flag[i])
for(int j=i*i; j<=24; j+=i)
flag[j]=1;
for(int i=2; i<=24; i++)
if(!flag[i])
isp[i]=1;
}//素数打表
void iniT(int n)
{
for(int i=0;i<n;++i)
{
a[i]=i+1;
}
}
void dfs(int x)
{
if(x==n&&isp[a[0]+a[n-1]])//判断第一个数和最后一个数是否成立
{
for(int i=0;i<n;++i)
printf("%d%c",a[i],i==n-1?'\n':' ');
}
else
for(int i=2;i<=n;++i)
{
if(!index[i]&&isp[i+a[x-1]])//首先,两个数的和为素数,这个时候赋值
{
a[x]=i;
index[i]=1;
dfs(x+1);
index[i]=0;//代表走不通
}
}
}
int main()
{
is_prime();
while(~scanf("%d",&n))
{
printf("Case %d:\n",++num);
iniT(n);
memset(index,0,sizeof(index));
dfs(1);
printf("\n");
}
return 0;
}
在写这段代码之前,我对于这道题怎么写代码还是一脸懵逼,后来想了半个多小时,然后看了看我以前写的代码开始写半个小时,发现对照以前的代码写还有好多BUG,修改了几十分钟才写出来。emmmm
从上往下,这个代码分为四部分:1.素数的函数 2.赋值的函数 3.dfs递归的调用 4.主函数。
这个是在主函数里面考虑的,每次输入都要输出Cse X:,既然X是变化的,那么就在每进行一层while循环的时候让X+1,并且后面加一个换行符
前面说index是判断一组数里面的某一个数是否用过,那么在每次的while循环开始(每次输入数据),index应该赋初值0,这样才不会出现错误。memset(index,0,sizeof(index));
并且每次都要用 iniT(n);对a进行赋值
递归的调用,从第一个数开始,dfs(1);然后往后判断。
关于这个图,意思就是从2开始,判断这个数与前面一个数的和是否是素数,你看,如果成立的话,直接走到头了,而如果不成立的话只能走一点点就走不通了,那么怎么才能把走得通与走不通表示出来呢?
首先,判断是不是已经判断了n个数,如果说判断了n个数就输出。
其次,如果说没有成立,那么该怎么办?
粗略地说就是返回上一级
if(!index[i]&&isp[i+a[x-1]])//首先,两个数的和为素数,这个时候赋值
{
a[x]=i;
index[i]=1;
dfs(x+1);
index[i]=0;//代表走不通
}
也就是index[i]=0,代表这个数没有被使用
a[x]是记录可行的序列(所以用x作为下标,因为x是从1开始的)
注意:这里的index的下标是i,而不是x,为什么呢?
因为for循环,for(int i=2;i<=n;++i)。
看着个图的计算轨迹,你仔细思考思考,应该就能想出来。
这个是从小到大进行的。
dfs(x+1):就代表了一个数成立后的下一个数。
index[i]=0:代表了这个数没有被用过
比如说:上图中最上面的那个中的1 2 3 轨迹,假设2 3 是不成立的,那么index【2】就应该变成0,代表这个数没有用过。