素数环
-
描述
-
有一个整数n,把从1到n的数字无重复的排列成环,且使每相邻两个数(包括首尾)的和都为素数,称为素数环。
为了简便起见,我们规定每个素数环都从1开始。例如,下图就是6的一个素数环。
-
输入
- 有多组测试数据,每组输入一个n(0<n<20),n=0表示输入结束。 输出
-
每组第一行输出对应的Case序号,从1开始。
如果存在满足题意叙述的素数环,从小到大输出。
否则输出No Answer。
样例输入
-
6 8 3 0
样例输出
-
Case 1: 1 4 3 2 5 6 1 6 5 2 3 4 Case 2: 1 2 3 8 5 6 7 4 1 2 5 8 3 4 7 6 1 4 7 6 5 8 3 2 1 6 7 4 3 8 5 2 Case 3: No Answer
思路:
回溯法(DFS)。如果给出数为奇数,则必有其中两个奇数相邻,那么相加则为偶数,所以n为奇数时,输出No Answer。而当n为偶数时,必然会存在符合素数环的序列。当n等于1时,也能输出,这时候1形成自环。
AC:
#include<stdio.h> #include<string.h> int prime[40]; //考虑最大相邻相加数的情况,18+19=37,所以把边界定为40 int vis[20],pos[20]; //vis登记当前数是否已经访问,pos为序列的位置 void DFS(int k,int n) { if(k==n+1&&prime[pos[1]+pos[n]]) //判断退出的条件 //注意不要忘了判断最后一个和第一个相加是否为素数 { for(int j=1;j<=n;j++) { printf("%d",pos[j]); j==n?printf("\n"):printf(" "); } return; } else { for(int i=2;i<=n;i++) { if(!vis[i]&&prime[i+pos[k-1]]) //这里是k-1,因为是逐个向后添加的,所以应该是判断添加的数和上一个数的和是否为素数 //未访问,且这个数与上一个数相加和为素数 { vis[i]=1; pos[k]=i; DFS(k+1,n); vis[i]=0; //记住要清楚标记 } } } } int main() { int n,times=1; memset(prime,0,sizeof(prime)); prime[2]=1,prime[3]=1,prime[5]=1,prime[7]=1,prime[11]=1,prime[13]=1; prime[17]=1,prime[19]=1,prime[23]=1,prime[29]=1,prime[31]=1,prime[37]=1; //素数表 while(scanf("%d",&n)!=EOF&&n) { printf("Case %d:\n",times++); if(n==1) printf("1\n"); else { if(n%2) printf("No Answer\n"); else { pos[1]=vis[1]=1; memset(vis,0,sizeof(vis)); DFS(2,n); } } } return 0; }
总结:
1.理解递归,弄清整个循环的过程;
2.一开始先可以将n分为奇数和偶数的情况;
3.输出的结果不一定只有一种,只要满足条件就能输出来。
回溯法
定义:
回溯法也称试探法,它的基本思想是:从问题的某一种状态(初始状态)出发,搜索从这种状态出发所能达到的所有“状态”,当一条路走到“尽头”的时候(不能再前进),再后退一步或若干步,从另一种可能“状态”出发,继续搜索,直到所有的“路径”(状态)都试探过。这种不断“前进”、不断“回溯”寻找解的方法,就称作“回溯法”。
基本思想:
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。 若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。