描述
有一个整数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
思路:题目意思是给出一个n,在[1,n]中的数中的全排列组成一个环,其中相邻的元素的和是素数。简单明了的思路就是枚举出所有的全排列,找出符合要求的即可。用next_permutation()。代码这样写:
for(int i=0 ;i<10000 ;i++){ a[i] = i+1; } while(scanf("%d",&n)!=EOF&&n){ do{ int ok = 1; for(int i=0 ;i<n ;i++){ if(!isPrime(a[i]+a[(i+1)%n])){ ok = 0; break; } } if(ok){ for(int i=0 ;i<n ;i++){ printf("%d ",a[i]); } } }while(next_permutation()); }
但是全排列的个数多达 20!,其中有跟多都是无效的环。所以改变思路:用DFS,递归求排列,加上限制条件以后,即(回溯)。当更新排列的下一个数据时,如果没有能和前一个数的和是素数的情况,即无法找出合法的排列,则递归返回上一层。这样就减少了不必要的搜索。
题目中要求:如果无法得到结果,则输出No Answer 我首先想到的是声明一个标志变量flag,在dfs函数中的递归边界中更新flag的数值,递归完成后,判断flag值是否被更新,如果没有则输出No Answer 。代码:
void dfs(int cur){ int flag = 0; if(cur==n&&isPrime(a[cur-1]+1)){ flag = 1; for(int i=0 ;i<n ;i++){ printf("%d ",a[i]); } puts(""); }else{ for(int i=2 ;i<=n ;i++){ if(!vis[i]&&isPrime(i+a[cur-1])){ a[cur] = i; vis[i] = 1; dfs(cur+1); vis[i] = 0; } } }
提交以后TL 超时。。。楼主当时有点懵,不知道该怎么优化,然后看了下自己写的判断素数的函数
n最大是20,循环数据也不算太大,所以也不应该是这里的问题,这道题是用递归写的,时间消耗最大的地方就是递归上面,那么就想办法优化算法。bool isPrime(int a){ if(i==0||i==1)return false; for(int i=2 ;i*i<=n ;i++){ if(a%i==0) return false; } return true; }
观察No Answer的答案时候输入的n 都是奇数(除1外),那么在进行数据处理前先对n进行判断,如果是奇数,则直接输出No Answer 如果是1或者是偶数,则进行递归求解。这样减少一般的时间消耗。
AC代码:
#include<cstdio> #include<iostream> #include<string> #include<cstring> #include<cmath> #include<algorithm> using namespace std; int n; int vis[10000]; int a[10000]; int prime[12] = {2,3,5,7,11,13,17,19,23,29,31,37}; bool isPrime(int a){//判断a是否是素数 for(int i=0 ;i<12 ;i++){ if(a==prime[i]) return true; } return false; } void dfs(int cur){ if(cur==n&&isPrime(a[cur-1]+1)){ //递归边界 for(int i=0 ;i<n ;i++){ printf("%d ",a[i]); } puts(""); }else{ for(int i=2 ;i<=n ;i++){ if(!vis[i]&&isPrime(i+a[cur-1])){ a[cur] = i; vis[i] = 1; //设置使用标志 dfs(cur+1); vis[i] = 0; //清除标志 } } } } int main(){ int cnt = 1; a[0] = 1; while(scanf("%d",&n)!=EOF&&n){ memset(vis,0,sizeof(vis));//数组一定要清零 printf("Case %d:\n",cnt++); if(n%2==0||n==1) dfs(1); else printf("No Answer\n"); } return 0; }