Prime ring problem - 九度 OJ 1459
题目
时间限制:2 秒 内存限制:32 兆 特殊判题:否
题目描述:
A ring is compose of n circles as shown in diagram. Put natural number 1, 2, …, n into each circle separately, and the sum of numbers in two adjacent circles should be a prime.
Note: the number of first circle should always be 1.
输入:
n (1 < n < 17).
输出:
The output format is shown as sample below. Each row represents a series of circle numbers in the ring beginning from 1 clockwisely and anticlockwisely. The order of numbers must satisfy the above requirements. Print solutions in lexicographical order.
You are to write a program that completes above process.
Print a blank line after each case.
样例输入:
6
8
样例输出:
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
题目大意为由给定的 1 到 n 数字中,将数字依次填入环中,使得环中任意两个相邻的数字间的和为素数。对于给定的 n,按字典序由小到大输出所有符合条件的解(第一个数恒定为 1)。这就是有名的素数环问题。
为了解决该问题,我们可以采用回溯法枚举每一个值。当第一个数位为 1确定时,我们尝试放入第二个数,使其和 1 的和为素数,放入后再尝试放入第三个数,使其与第二个数的和为素数,直到所有的数全部被放入环中,且最后一个数与 1 的和也是素数,那么这个方案即为答案,输出;
若在尝试放数的过程中,发现当前位置无论放置任何之前未被使用的数均不可能满足条件,那么我们回溯改变其上一个数,直到产生我们所需要的答案,或者确实不再存在更多的解。
为了实现这一回溯枚举的过程,我们采用递归的形式。
#include <stdio.h>
#include <string.h>
int ans[22];//保存环中每一个被放入的数
bool hash[22];//标记之前已经被放入环中的数
int n;
int prime[]={3,5,7,11,13,17,19,23,29,31,37};
//素数,若需判断一个数是否为素数则在其之中查找
//因为输入大于1,小于17,故两数和构成的素数必在该数组内
bool judge(int x){
//判断一个数是否为素数
for(int i=0;i<11;i++){
if(prime[i] == x)return true;
//在素数数组中查找,若查找成功则该数为素数
}
return false;//否则不是素数
}
void check(){
//检查输出由回溯法枚举得到的解
if(judge(ans[n]+ans[1])==false)return;
//判断最后一个数与第一个数的和是否为素数,若不是则直接返回
for(int i=1;i<=n;i++){
//输出解,注意最后一个数字后没有空格
if(i!=1)printf(" ");
printf("%d",ans[i]);
}
printf("\n");
}
void DFS(int num){
//递归枚举,num为当前已经放入环中的数字
if(num>1){
//当放入的数字大于一个时
if(judge(ans[num]+ans[num-1])==false)return;
//判断最后两个数字的和是否为素数,若不是则返回继续枚举第num个数
}
if(num==n){
//若已经放入了n个数
check();//检查输出
return;//返回,继续枚举下一组解
}
for(int i=2;i<=n;i++){
//放入一个数
if(hash[i]==false){
//若i还没有被放入环中
hash[i]=true;//标记i为已经使用
ans[num+1]=i;//将这个数字放入ans数组中
DFS(num+1);//继续尝试放入下一个数
hash[i]=false;//当回溯回枚举该位数字时,将i重新标记为未使用
}
}
}
int main()
{
int cas=0;//记录case数
while(scanf("%d",&n)!=EOF){
cas++;//Case数递增
for(int i=0;i<22;i++){
hash[i]=false;
//初始化标记所有数字为未被使用
}
ans[1]=1;//第一个数字恒定为1
printf("Case %d:\n",cas);//输出Case数
hash[1]=true;//标记1被使用
DFS(1);//继续尝试放入下一个数字
printf("\n");//输出换行
}
return 0;
}
补充说明下方代码片段:
hash[i] = true;
ans[num + 1] = i;
DFS(num + 1);
hash[i] = false;
在尝试放入第 num+1 个数字时,我们依次尝试放入所有在之前位置上未被使用的数字,假设当前 x 未被使用,我们将 x 放入第 num+1 个位置,标记 x 为已用,此时环中前 num+1 个数字全部确定,依次保存在 ans[1]到 ans[num+1]中,再进行下一个位置的枚举,即递归调用 DFS。
当调用返回时,意味着当前 num+1个数字确定为ans[1]到ans[num+1]中的值时对应的所有可行的答案已经全部处理完毕(可能一个解也不存在),此时我们需要改变 ans[num + 1]的值,从而进行新的答案的搜索。所以,此时 ans[num + 1]的值将不再为已经被枚举过的 x,而是一个相异于 x,同时又未在之前被使用过的新数字。那么对于后序数字而言,x是未被使用的,是可以被放入后序的任意一个位置的,所以我们重新标记 x 为未使用,供后序数位选择。这就是为什么,我们先是标记 x 为已经使用,而后又解除该标记的原因。