1. 程式人生 > 實用技巧 >POJ 3126 Prime Path

POJ 3126 Prime Path

題目如下:

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;
	}
}