POJ 3126 Prime Path
阿新 • • 發佈:2020-12-06
題目如下:
hh學長酷愛素數,他經常自娛自樂,隨機挑選兩個四位的素數a,b。
遊戲規則是:a可以通過改變某一位上的數字使其變成c,但只有當c也是四位的素數時才能進行這種改變。
hh學長可以很輕鬆的算出a最少經過多少次變化使其變為b,所以他相信你也可以。
例如:1033 -> 8179
1033
1733
3733
3739
3779
8779
8179
最少變換了6次。
Input
第一行輸入整數T,表示樣例數。 (T <= 100)
每個樣例輸入兩個四位的素數a,b。(沒有前導零)
Output
對於每個樣例,輸出最少變換次數,如果無法變換成b則輸出"Impossible"。
Sample Input
3
1033 8179
1373 8017
1033 1033
Sample Output
6
7
0
解題思路:
因為這道題求的是最少變換次數,所以這道題也是用bfs進行求解。並且這道題是要從一個素數變化到另一個素數,並且每一次變化時都需要是素數。所以,我們需要將一定範圍內的所有素數求出來放在一張表中。我們這裡採用埃拉托色尼篩法來進行篩出素數。不知道埃拉托色尼篩法的話,下方給出筆記。
https://www.cnblogs.com/gao79135/p/14092390.html
這道題從一個素數變化到另一個素數時,由於素數都是四位,且每一個位上都可以變化10次(0-9),所以從一個素數變化到另一個素數時共有40種變化方式。並且我們採用字串來儲存輸入的素數,這樣在變化的時候我們只需要利用下標來進行變化就好了。(如果要採用純數字儲存的話,需要求出各個位上的數字,並且還得進行相乘相加組成新的數字,未免有些麻煩) 由於這道題需要求出最小的變化次數,因此我們可以利用記憶化搜尋來進行求解。將所有搜尋到的素數都標記上得到該素數之前的素數(代表這個搜尋到的素數是由上一個素數變化而來的),當發現可以有素數a變化到素數b時,我們就可以通過遞迴來回到之前的素數直到返回剛開始的素數。這樣的話遞迴的次數就是最少的變換次數。由於採用記憶化搜尋,因此我們需要使用陣列來模擬佇列,將搜尋到的素數全部放在陣列當中,以便於遞迴時可以呼叫原先搜尋到的素數。由於在搜尋的時候,可能會搜尋到同樣的素數數次,因此我們需要設定一個訪問陣列。搜尋到了一個素數就在訪問陣列中中進行標記,以防止重複新增到佇列中。如果不明白的話,就看一下程式碼吧!
程式碼如下:
#include <iostream> #include <cstdio> #include <cstring> #include <string> using namespace std; typedef struct { string s; //定義每個點的素數 int pre; //定義當前狀態的之前狀態在陣列中的下標 }Point; Point Q[10000]; //定義佇列(因為本題中素數的範圍為1000-9999) int T; //定義樣例數、兩個素數 string a, b; //見上 bool isprime[10002]; //定義素數表 bool status[10000]; //定義訪問陣列 int Count = 0; //最少變換次數 void eratos(int n); //定義埃拉托色尼篩法生成素數表的函式 Point bfs(); //定義進行bfs的函式 void dfs(Point end_point); //定義進行遞迴的函式 int s_int(string s); //代表將string轉為int的函式 void init(); //代表進行初始化的函式 void init() { int i; for (i = 0; i < 10000; i++) { status[i] = false; } } int s_int(string s) { int ret = 0; for (int i = 0; i < 4; i++) { ret *= 10; ret += (s[i] - '0'); } return ret; } void eratos(int n) { int i, j; for (i = 0; i < n; i++) { isprime[i] = true; } isprime[0] = isprime[1] = false; for (i = 2; i < n; i++) { if (isprime[i]) { j = i + i; while (j <= n) { isprime[j] = false; j = i + j; } } } } Point bfs() { int i, j; int front = 1, now = 0; //定義移動的指標front和當前狀態的下標now string temp; //定義當前遍歷的數 Q[0].s = a; Q[0].pre = -1; status[s_int(a)] = true; //起點已經被訪問過 while (true) { if (now > front) { break; } for (i = 0; i < 4; i++) //代表位數(0為千位,1為百位,2為十位,3為個位) { for (j = 0; j < 10; j++) //代表變化的數字(0-9) { if (i == 0 && j == 0) //如果千位上的數字為0 { continue; } temp = Q[now].s; temp[i] = j + '0'; //進行數字的變化(將第i位變化為數字j+'0') if (isprime[s_int(temp)] == true && status[s_int(temp)] == false) //如果變化後的數字是素數且該數字沒有訪問過 { Q[front].s = temp; Q[front].pre = now; status[s_int(temp)] = true; //代表該數已經被訪問過 front++; } } } if (Q[now].s == b) { return Q[now]; } now++; } Point bad; //代表無法變換成b的情況 bad.pre = -10000; return bad; } void dfs(Point end_point) { if (end_point.pre != -1) { Count++; } if (end_point.pre == -10000) { cout << "Impossible" << endl; return; } if (end_point.pre == -1) { return; } dfs(Q[end_point.pre]); //遞迴到該狀態之前的狀態 } int main() { int i; Point end_point; eratos(10000); scanf("%d", &T); for (i = 0; i < T; i++) { init(); cin >> a >> b; end_point = bfs(); dfs(end_point); cout << Count << endl; Count = 0; } }