Prime ring problem - 九度 OJ 1459

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 为已经使用,而后又解除该标记的原因。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值