题解(图解) HDU 1016 Prime Ring Problem

原创文章: 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 1n20

质数, 也叫做素数. 是指在大于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 n20 比较小, 用计算机来模拟一个一个串珠子, 这个方法确实可以行得通.

假设有 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值