NYOJ - 488 - 素数环(回溯法)

描述

有一个整数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 超时。。。

楼主当时有点懵,不知道该怎么优化,然后看了下自己写的判断素数的函数

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;
}
n最大是20,循环数据也不算太大,所以也不应该是这里的问题,这道题是用递归写的,时间消耗最大的地方就是递归上面,那么就想办法优化算法。

观察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;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值