原创文章: https://www.huilon.net.cn/article/20
HDU1016
Description
给定一串数字 1 , 2 , . . . n 1,2,...n 1,2,...n要求用这 n n n 个数字串成一个环, 使得环上所有相邻的两个数字相加都是素数(或者叫质数)
现在求环上的数字排列, 把所有的可能结果都求出来, 输出的时候先输出 1 1 1, 多个结果的时候按排列的字典序从小到大输出.
范围:
1 ≤ n ≤ 20 1≤n≤20 1≤n≤20
质数, 也叫做素数. 是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
自然数是指表示物体个数的数,即由0开始,0,1,2,3,4,……一个接一个,组成一个无穷的集体,即指非负整数。
有些国家的教科书是把 0 0 0 也算作自然数的。这本是一种人为的规定,我国为了推行国际标准化组织(ISO)制定的国际标准,定义自然数包含元素 0 0 0.
取自百度百科, 有改动.
取自百度百科, 有改动.
在数学中,字典或词典顺序,是指在 n 个数字
1 , 2 , 3... , n 1,2,3...,n 1,2,3...,n
的排列中,由从左到右逐个数字比较的大小。例如对于 1 , 2 , 3 , 4 , 5 1,2,3,4,5 1,2,3,4,5 的两个排列 12354 12354 12354和 12345 12345 12345,就有
12345 12345 12345
比排列12354 12354 12354
要小。按照这样的规定,5 个数字的所有的排列中最小的是 12345 12345 12345,最大的是 54321 54321 54321.取自百度百科, 有改动.
Tutorial
咱们用串珠子的方法来做这道题. 因为 n ≤ 20 n≤20 n≤20 比较小, 用计算机来模拟一个一个串珠子, 这个方法确实可以行得通.
假设有 6 6 6 个珠子, 首先是把 1 1 1 这个珠子串上. 之后剩下 5 5 5 个珠子可供我们选择, 这个时候随便选一个跟 1 1 1 加起来是质数的, 如下图:
因为题目要求要从小到大输出排列, 所以选最小的 2 2 2 这个珠子, 2 + 1 = 3 2+1=3 2+1=3, 3 3 3 是质数, 可以. 接下来还有 4 4 4 个珠子可以选, 如下图:
就这样一直串下去, 直到串完 4 4 4 这个时候, 后面的 5 5 5 和 6 6 6 都不符合要求, 咱们都不能选. 如下图
说明咱们选的 4 4 4 这个数字是错误的, 把它拔出来, 回过头去选其他的, 如下图:
这个时候对于 3 3 3 来说, 就是要选 5 5 5 或者 6 6 6, 不过加起来都不是质数, 也不行, 如下图:
在把 3 3 3 拔出来, 去选其他的, 对于 2 2 2 来说, 4 4 4 加起来不是质数, 不能选, 再下来的 5 5 5 加起来等于 7 7 7 是质数, 可以选. 再往里探索, 就有下图:
发现还是走到死胡同了. 现在咱们要做的就是把 6 , 5 6,5 6,5 这两个珠子拔出来, 然后发现 2 + 6 2+6 2+6 不是质数, 那把 2 2 2 这个数字也给拔了. 如图:
就这样一直串下去, 直到串到 1 , 4 1,4 1,4 的时候, 如图:
再往里探索的时候, 发现有一种情况把珠子串完了, 如图:
这个 1 , 4 , 3 , 2 , 5 , 6 1,4,3,2,5,6 1,4,3,2,5,6 就是咱们要的第一个排列, 往后, 还发现了另外一个排列 1 , 6 , 5 , 2 , 3 , 4 1,6,5,2,3,4 1,6,5,2,3,4 如图:
所以咱们用程序模拟串珠子的流程就好了.
不过上面是两种不是很典型的例子. 考虑一下如果串完的最后一个珠子是 3 3 3, 3 3 3 和开头的珠子 1 1 1 加起来可不是质数, 这个时候的排列不是题目要求的, 要把它舍弃掉.
每个串珠子的时候都是一样的流程, 并且还是在串了一个珠子的基础上继续串下一个珠子, 这样一遍一遍重复的过程, 咱们想到就是用递归来模拟串珠子, 直到发现珠子串完了, 我们才结束递归, 否则, 就把剩下的珠子, 一个一个串下去.
有以下代码:
#include <iostream>
#include <stdio.h>
#include <vector>
#include <cstring>
#include <set>
using namespace std;
set<int> primeSet = {3,5,7,11,13,17,19,23,29,31,33,37};
// 题目给的数字最大是 20, 20+20=40, 40以下的质数, 就这么点, 可以手写出来.
#define isPrime(x) (primeSet.count(x) > 0) // 判断是否是质数
vector<int> ring;
// 已经加入的数字, 可以把它当做项链, 存储的已经串上的珠子.
bool isOnTheRingOf[21];
// 当前探索过的数字, 为了提高运行速度, 把串上珠子的数字赋值为 true
// 即 isOnTheRingOf[number] = true; 如果要把珠子拔出来, 既要赋值为 false
/**
* @参数 resultList 结果二维数组, 可以把他当做是多条项链
* @参数 currentNum 当前串上的最后一个珠子
* @参数 maxNum 一共有多少个珠子
*/
void explore(vector<vector<int> >& resultList, int currentNum, int& maxNum) {
// 递归程序. 模拟串珠子的流程
if(ring.size() == maxNum) {
// 这个时候是串完了珠子
if(isPrime(ring.back() + ring.front())) {
// 咱们只要最后一个珠子跟第一个珠子加起来是质数的
resultList.emplace_back(ring);
// emplace_back 是一个在 resultList 最后添加元素的函数
// 可以把他当做加入 ring 这条项链. (会把他拷贝一份再放进去)
}
// 代码走到这一行的时候, 说明珠子串完了, 但是不符合要求, 要舍弃. (递归结束)
} else {
// 否则就是还有剩下的珠子可以串
for (int targetNum = 2; targetNum <= maxNum; ++targetNum) {
// 咱们遍历 2 ~ 20 的数字
if(!isOnTheRingOf[targetNum]
&& isPrime(currentNum + targetNum)) {
// 把还没有串的, 即 isOnTheRingOf[targetNum] == false,
// 并且跟当前最后串上的数字加起来是质数的选出来.
// (跳过 1 这个数字是因为 1 这个珠子一定是串上了的)
ring.emplace_back(targetNum); // 串上
isOnTheRingOf[targetNum] = true;
explore(resultList, targetNum, maxNum); // 然后再来看看接下来可以选谁
// (递归结束时将会走到这一行)
// 代码走到这一行的时候, 说明一个珠子都没得选, 或者是珠子串完了不符合要求, 要舍弃,
// 那就要把当前串上的珠子给拔出来
ring.pop_back(); // 拔出来
isOnTheRingOf[targetNum] = false;
// 继续 for 循环看看下一个珠子怎么样
}
}
// 这里是 for 的结尾, 如果这个时候 for 里面的 if 一个都没有满足, 说明一个珠子都没有串上 (递归结束)
}
}
int main() {
memset(isOnTheRingOf, 0, sizeof(isOnTheRingOf));
ring.emplace_back(1); // 先把 1 这个珠子串上
int caseCount = 1;
int n;
while(cin >> n) {
cout << "Case " << caseCount << ":" << endl;
vector<vector<int> > results;
explore(results, 1, n); // 在串上 1 这个珠子的基础上, 看看可以选谁来串下一个
for(auto &result : results){
for (int i = 0; i < n; ++i) {
if(i != 0) cout << " ";
cout << result[i];
}
cout << endl;
}
cout << endl;
caseCount ++;
}
return 0;
}