nyoj ACM:素數環(DFS 回溯 遞迴)
素數環
時間限制:1000 ms | 記憶體限制:65535 KB
難度:2
描述
有一個整數n,把從1到n的數字無重複的排列成環,且使每相鄰兩個數(包括首尾)的和都為素數,稱為素數環。
為了簡便起見,我們規定每個素數環都從1開始。例如,下圖就是6的一個素數環。
輸入
有多組測試資料,每組輸入一個
輸出
每組第一行輸出對應的Case序號,從1開始。
如果存在滿足題意敘述的素數環,從小到大輸出。
否則輸出No Answer。
樣例輸入
6
8
3
0
樣例輸出
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
Case 3:
No Answer
來源
hdu改編
上傳者
ACM_丁國強
思路:
素數環(還有後面 部分和問題)都是用來回溯演算法的思想,而且回溯演算法使用 dfs 實現的, 而dfs是用遞迴 寫得(還有剪枝)。
在我看來,在遞迴中遞迴的退出條件就是剪枝操作
**
遞迴理解
**
漫談遞迴大神之筆
對於遞迴,最好的理解方式便是從函式的功能意義的層面來理解。瞭解一個問題如何被分解為它的子問題,這樣對於遞迴函式程式碼也就理解了。這裡有一個誤區(我也曾深陷其中),就是通過分析堆疊,分析一個一個函式的呼叫過程、輸出結果來分析遞迴的演算法。這是十分要不得的,這樣只會把自己弄暈,其實遞迴本質上也是函式的呼叫,呼叫的函式是自己或者不是自己其實沒什麼區別。在函式呼叫時總會把一些臨時資訊儲存到堆疊,堆疊只是為了函式能正確的返回,僅此而已。我們只要知道遞迴會導致大量的函式呼叫,大量的堆疊操作就可以了。
遞迴的框架
對於遞迴,用函式的功能來理解遞迴,這樣才能夠保證不出錯。
DFS( , )//名稱是DFS(),但並不僅僅適用於子問題
{
//最開始的是遞迴在簡單情境下的退出,這一部分必須有,是遞迴的重要組成部分
//可以理解為剪枝
if(..)
return;
if(...)
{
...
...
return;
}
//下一部分就是不滿足簡單情形的情況
//說明此時還不是簡單情形,那麼就要繼續將改問題減少成更小的子問題
.....
DFS()//化成更小的子問題
.....
}
int main()
{
DFS(... , ...);
}
本題程式碼:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n;
int prime[40]={0,0,1,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0};
//prime[i]表示i是否為素數
int a[30];
int visited[30];
int flag=0;
void DFS(int lest)
{
if(lest>n)//第一個退出條件
return;
if(lest==n)//第二個退出條件
{
if(prime[a[n]+1]==1)
{
flag=1;
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
printf("\n");
}
}
//以上兩個條件都是剪枝的內容
for(int i=1;i<=n;i++)
{
if(visited[i]==0 && prime[a[lest]+i]==1)
{
a[lest+1]=i;
visited[i]=1;
DFS(lest+1);
visited[i]=0;
}
}
}
int main()
{
int i=0;
while(scanf("%d",&n)!=EOF && n!=0)
{
memset(visited,0,sizeof(visited));
memset(a,0,sizeof(a));
i++;
flag=0;
printf("Case %d:\n",i);
a[1]=1;
visited[1]=1;
if(n==1)//特殊情況
{
printf("%d\n",1);
continue;
}
if(n%2==0)//奇數不可能成立,也是剪枝內容
DFS(1);
if(flag==0)
printf("No Answer\n");
}
return 0;
}