一、问题描述
枚举符合如下条件的由数字 1 ~ n 构成的环形串(1 ≤ n ≤ 16):
1、第一个数为 1 。
2、任意相邻的两个数的和为质数。
输出时可以任意选择起点,逆时针或顺时针输出。
每组数据输入一个数 n ,结束标志为 EOF 。每两个输出之间要空一行。每组输出的开头加上如下内容:
Case t:
t 即第 t 个样例。
二、算法分析说明与代码编写指导
通过递归来构造这个序列的每一个数。记该数列为 a[17] ,且 a[0] = 1 。那么我们从 a[1] 开始确定。
递归的终止条件一般都写在递归函数的最前面。这里传入参数是正在构造的元素的下标 h 。当 h = n 并且第 0 个数与第 n - 1 个数的和也是质数时,输出这个数列。终止条件之一为 h = n 的原因类似循环条件为 i < c (c 是常数)、第一次循环后每开始下一次循环前都要 ++i 的 for 循环中必须先跳到循环开头令 i = c 才能结束循环。
已知 n ≤ 16 ,相邻两数的和最大为 31 ,可以先手动构造一个 bitset<32> isprime 代表 0 ~ 31 中每个数是否为质数,方便查询。注意用 string 构造 bitset 时,需要从高位往低位写。
当递归结束的条件不满足时,就从 a[1] 开始确定数列的剩余项。由于第 0 项一定为 1 ,所以我们尝试将 2 ~ n 填入数列即可。
用 bitset<17> visited 来表示数 1 ~ 16 是否已经填入。
当数 i 未被填入数列,且一旦在 a[h] 填入 i ,a[h] + a[h - 1] 为质数,就填入该数。填完该数以后,标记 visited[i] = 1 ,然后调用 generate(h + 1) 进入深一层递归开始确定下一项。当深一层的递归完成后,要将本层的 visited[i] 改回 false ,相当于写完一个解后擦掉第 h 项之后的数,以备重新填入下一个符合的解。
当在屏幕上输出一个解后,该层递归执行完毕自动返回。我们知道每次递归都会扫描数 2 ~ n 考察有哪些数还没被填入。可以发现,如果没有跑完全部的情况(无论这组解可以输出还是因为不符合要求而不能输出),那么肯定在中间某一层递归时会有部分数还未验证过。在递归返回到这一层后,就会继续执行循环去验证这些数能否构成下一个解。跑到接近全部组合验证完毕时,从最深层递归开始,每一层递归都会验证 visited[n] == true 以后又因为 i > n 而陆续返回,从而最终得以结束该算法。
这就是本题的主要算法的原理。
当然,还可以根据 https://blog.youkuaiyun.com/mobius_strip/article/details/36624421 中的方法进行剪枝。我们知道奇数 + 奇数 = 偶数,奇数 + 偶数 = 奇数,偶数 + 偶数 = 偶数。而该链中每一个正整数都是不同的,所以相邻两数的和不可能为 2 。那么要使任意相邻两数的和为质数,任意相邻两数的和只能都是奇数。如果 n 为奇数,可以发现,总是会出现至少 1 对相邻的奇数,这时候这两个数加起来为偶数且一定 > 2 ,不可能是质数。所以 n 为奇数时可以仅输出样例编号而不输出任何序列(即无解)。
三、AC 代码(120 ms):
#include<cstdio>
#include<cstdlib>
#include<bitset>