1. 程式人生 > 其它 >HDU1016--素數環問題

HDU1016--素數環問題

技術標籤:資料結構

HDU1016–素數環問題

目錄

一、題目連結

HDU1016

二、題目內容

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.

在這裡插入圖片描述

Input

n (0 < n < 20).

Output

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.

Sample Input

6
8

Sample Output

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

翻譯

圓環由n個圓組成,如圖所示。將自然數1、2,…,n分別放入每個圓,兩個相鄰圓中的數字總和應為質數。
注意:第一個圓的數目應始終為1。
輸出格式如下所示。每一行代表環中從順時針和逆時針1開始的一系列圓圈編號。數字順序必須滿足以上要求。按字典順序列印解決方案。

您將編寫一個完成上述過程的程式。
在每種情況下都列印空白行。

三、題意拆解

這次的題目非常的簡潔明瞭。

將自然數1、2,…,n分別放入每個圓,兩個相鄰圓中的數字總和應為質數。
注意:圓的起始應始終為1。

這句話告訴我們三個資訊:

  1. 從1到n的數要排成一個首尾相連的圓環,即第一個數和最後一個數字是相連的。
  2. 任意兩個相鄰的數字之和要是一個素數。
  3. 所有的輸出數列要以1開頭。

好,那麼我們直接開始解題。

四、解題思路

首先,題目要求任意兩個相鄰的數字和是素數,並且要能組合成一個環用完所有的數。
那麼我們第一反應是什麼?依次把1後面的數字塞進去佇列,看看他和前一個數能否構成一個素數,能的話就更新這串數列,不能的話就換到下一個數字塞進去。更新完數列後從頭重新把數列塞進去,直到數列長度等於n或者有有數字塞不進去的情況。
讓我們來看個例子。
比如我們輸入

6

這時候我們先把佇列初始化,此時佇列為

1

然後我們看看2能不能塞到1後面,1+2=3是素數,可以塞進去,更新佇列

1 2

然後看看3能不能塞進去,2+3=5是素數,塞進去,更新佇列

1 2 3

然後看看4能不能塞進去,3+4=7是素數,塞進去,更新佇列

1 2 3 4

依次類推,5+4=9不是素數,檢視下一個
6+4=10不是素數,但此時已經沒有下一個數可以去塞了,證明這個排序是不行的,那我們倒回上一個數字
此時佇列回溯到

1 2 3

因為4會導致後續的數列無法完善,我們就繼續看下一個數
3+5=8不是素數,下一個
3+6=9不是素數,但後面又沒有數字了,我們繼續回溯到上一個佇列
此時佇列回溯到

1 2

然後重複這樣子的行為,我們能夠找到第一條符合題意的佇列

1 4 3 2 5 6

然後我們輸出它,繼續找它的下一個。
這個就是這一題的具體思路了。那麼如何實現呢?
因為我們可能要回溯到上一步的佇列,所以我們如何儲存上一步的佇列這很關鍵。學過資料結構的人應該清楚,在c++裡面有這麼一個概念- -棧。這剛好就是我們所需要的儲存方式。
所以我們就用棧來儲存,那麼在資料結構裡面什麼方法就是用到棧呢?
沒錯就是遞迴,所以這一題我們用遞迴的思路來寫。
解決了儲存問題,接下來就是判斷問題了。
因為我們每一次相加減我們就需要判斷一次素數,因此我們最好是把判斷素數寫成一個函式,方便我們呼叫。
但是呢,呼叫函式也會佔用大量運算時間,因此我們可以提前把素數給標記出來,也就是打一個素數表。
我們建立一個數組,因為題目n最大為20,所以我們建立個41的陣列就夠了,初始化為0,然後把不是素數的標記為1.具體打表我在程式碼裡會有詳細註釋。
解決完這些大問題,接下來就是題目需要注意的細節了。
首先:題目是個多資料輸入,並且每次資料輸入都需要對應次序的標號
比如第一組資料我們要輸出

Case 1:

第二個要注意的地方就是在每次資料找出所有可行性佇列後,都要輸出一行空白行。
第三點,也是本人一直忽視的點,在每列資料輸出後不能有多餘的空格出現。
以上就是這題的具體解法和要點了。

五、參考程式碼

//
// Created by Verber.
//
#include "iostream"
#include "bits/stdc++.h"
#include "queue"
using namespace std;
int n;
int biao[42],zu[22],pd[22];//三個陣列分別儲存的是素數表,數列,以及判斷該數字是否入隊
void sushudabiao()//打表函式
{
    memset(biao,0,sizeof(biao));//初始化陣列
    for (int i = 2; i<= 42; i++)
    {
        if(biao[i]==0)//如果是素數,它的倍數一定不是素數
        {
            for(int t=i*2;t<=42;t+=i)
            {
                biao[t]=1;
            }
        }
    }

}
void zhaohuan(int num)//遞迴函式
{
    if (num == n && biao[zu[0] + zu[n - 1]]==0)//當遞迴層數等於n時並且首尾數相加是素數的情況下跳出遞迴,輸出數列
    {
        for (int i = 0; i <n-1; i++)
        {
            cout<<zu[i]<<" ";
        }
        cout<<zu[n-1]<<endl;//最後一個數字後面不能有多餘的空格
        return;
    }
    else
    {
        for (int j = 2; j <= n; j++)//迴圈去尋找下一個塞進去數列的數字
        {
            if (!pd[j] && biao[j + zu[num - 1]]==0)
            {
                zu[num] = j;//把該數字入列
                pd[j] = 1;//表示該數字已入列
                zhaohuan(num + 1);//如果該數字能塞到當前數列後面,重新查詢下一個數字
                pd[j] = 0;//當執行完上一步的遞迴後,無論能否成數列,都要讓該數字出列,查詢新的能塞到當前位置的數字,找新的數列。
            }
        }
    }
}
int main()
{
    sushudabiao();
    int i=1;
    while (scanf("%d",&n)!=EOF) {
        cout<<"Case "<<i++<<":"<<endl;
        zu[0] = 1;
        memset(pd,0,sizeof(pd));//每一步初始化判斷列表。可省略
        zhaohuan(1);
        cout<<endl;

    }
}