2018.1.27【POJ - 3126】解題報告(BFS,換門牌號,素數篩)

Prime Path
Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 24280   Accepted: 13417

Description

The ministers of the cabinet were quite upset by the message from the Chief of Security stating that they would all have to change the four-digit room numbers on their offices. 
— It is a matter of security to change such things every now and then, to keep the enemy in the dark. 
— But look, I have chosen my number 1033 for good reasons. I am the Prime minister, you know! 
— I know, so therefore your new number 8179 is also a prime. You will just have to paste four new digits over the four old ones on your office door. 
— No, it’s not that simple. Suppose that I change the first digit to an 8, then the number will read 8033 which is not a prime! 
— I see, being the prime minister you cannot stand having a non-prime number on your door even for a few seconds. 
— Correct! So I must invent a scheme for going from 1033 to 8179 by a path of prime numbers where only one digit is changed from one prime to the next prime. 

Now, the minister of finance, who had been eavesdropping, intervened. 
— No unnecessary expenditure, please! I happen to know that the price of a digit is one pound. 
— Hmm, in that case I need a computer program to minimize the cost. You don't know some very cheap software gurus, do you? 
— In fact, I do. You see, there is this programming contest going on... Help the prime minister to find the cheapest prime path between any two given four-digit primes! The first digit must be nonzero, of course. Here is a solution in the case above. 
1033
1733
3733
3739
3779
8779
8179
The cost of this solution is 6 pounds. Note that the digit 1 which got pasted over in step 2 can not be reused in the last step – a new 1 must be purchased.

Input

One line with a positive number: the number of test cases (at most 100). Then for each test case, one line with two numbers separated by a blank. Both numbers are four-digit primes (without leading zeros).

Output

One line for each case, either with a number stating the minimal cost or containing the word Impossible.

Sample Input

3
1033 8179
1373 8017
1033 1033

Sample Output

6
7
0

Source


【題目大意】

換門牌號,要求每次只能更換一位,且更換完之後的新數字要是素數,問更換到指定號碼的最少步數是多少。

【解題思路】

按個十百千位廣搜,<40入口,搜索的規則是,該數沒有被搜索過,且該數爲素數。由於是BFS,所以第一次搜索到最終更改的門牌號時一定是最少步數,輸出該值即可。如果廣搜入口全部遍歷一編還是未能搜索到最終更改的門牌號,即爲不可能,輸出Impossible。

要點:

1.由於要求是素數,所以個位只需要搜索奇數即可,以及千位避開0;

2.涉及素數,可以用最基礎的素數判斷(即從2開始逐個除,若有因子則返回false),或者使用素數篩先建立素數表然後直接查表(待補充)

【解題代碼】

素數傳統判斷

#include <queue>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include<algorithm>
#define maxn 10100
using namespace std;
int n,m; 
bool vis[maxn]; // 訪問標記
//int dir[4][2]={0,1,0,-1,1,0,-1,0}; // 方向向量
//int dir[8][2]={{1,1},{1,0},{1,-1},{0,1},{0,-1},{-1,1},{-1,0},{-1,-1}} ;
struct State // BFS 隊列中的狀態數據結構
{
	int x;
	int Step_Counter; // 搜索步數統計器
};
bool isPrime(int x)
{
	if(x==0||x==1)
		return false;
	else if(x==2||x==3)
		return true;
	else
	{
		for(int i=2;i<=(int)sqrt(x);i++)
		{
			if(x%i==0)
				return false;
		}
		return true;
	}
} 

State a[maxn];

bool CheckState(int s,State now) // 約束條件檢驗
{
	if(s!=now.x&&!vis[s]&&isPrime(s)) // 滿足條件	
	{
//		printf("next s=%d, step=%d\n",s,now.Step_Counter+1);
		return 1;
	}	
	else // 約束條件衝突
		return 0;
}

void bfs(State st)
{
	queue <State> q;
	State now,next; // 定義2個狀態,當前和下一個
	st.Step_Counter=0; // 計數器清零
	q.push(st); // 入隊	
	vis[st.x]=1; // 訪問標記
	while(!q.empty())
	{
		now=q.front(); // 取隊首元素進行擴展
		if(now.x==m) // 出現目標態,此時爲Step_Counter 的最小值,可以退出即可
		{
			printf("%d\n",now.Step_Counter);
			return;
		}
		//按照規則搜索下一組數
		for(int i=1;i<=9;i+=2)
		{
			int s=now.x/10*10+i;
			if(CheckState(s,now))
			{
				vis[s]=1;
				next.x=s;
				next.Step_Counter=now.Step_Counter+1;
				q.push(next); 
			}
		}
		for(int i=0;i<=9;i++)
		{
			int s = now.x / 100 * 100 + i * 10 + now.x % 10;
			if(CheckState(s,now))
			{
				vis[s]=1;
				next.x=s;
				next.Step_Counter=now.Step_Counter+1;
				q.push(next);
			}
		}
		for(int i=0;i<=9;i++)
		{
			int s=now.x/1000*1000+i*100+now.x%100;
			if(CheckState(s,now))
			{
				vis[s]=1;
				next.x=s;
				next.Step_Counter=now.Step_Counter+1;
				q.push(next);
			}
		}
		for(int i=1;i<=9;i++)
		{
			int s=i*1000+now.x%1000;
			if(CheckState(s,now))
			{
				vis[s]=1;
				next.x=s;
				next.Step_Counter=now.Step_Counter+1;
				q.push(next); 
			} 
		}
		q.pop(); // 隊首元素出隊
	}
	printf("Impossible\n");
 	return;
}

int main()
{
	int t;
	scanf("%d",&t);
		for(int i=0;i<t;i++)
		{
//			printf("%d  %d\n",t,n);
//			while(!q.empty()) q.pop();
			memset(vis,0,sizeof(vis));
			scanf("%d%d",&n,&m);
			struct State tmp;
			tmp.x=n;
			tmp.Step_Counter=0;
			bfs(tmp);
		}
	return 0;
}

非線性埃式素數篩

#include <queue>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include<algorithm>
#define maxn 10100
using namespace std;
int n,m; 
bool vis[maxn]; // 訪問標記
//int dir[4][2]={0,1,0,-1,1,0,-1,0}; // 方向向量
//int dir[8][2]={{1,1},{1,0},{1,-1},{0,1},{0,-1},{-1,1},{-1,0},{-1,-1}} ;
struct State // BFS 隊列中的狀態數據結構
{
	int x;
	int Step_Counter; // 搜索步數統計器
};
bool isPrime[maxn]; 
void solvePrime()  //埃式素數篩 
{
	memset(isPrime,1,sizeof(isPrime));
	isPrime[0]=isPrime[1]=false;
	for(int i=2;i<=maxn;i++)
	{
		if(isPrime[i])
		{
			for(int j=i*i;j<=maxn;j+=i)
			{
				isPrime[j]=false;
			}
		}
	}
}

State a[maxn];

bool CheckState(int s,State now) // 約束條件檢驗
{
	if(s!=now.x&&!vis[s]&&isPrime[s]) // 滿足條件	
	{
//		printf("next s=%d, step=%d\n",s,now.Step_Counter+1);
		return 1;
	}	
	else // 約束條件衝突
		return 0;
}

void bfs(State st)
{
	queue <State> q;
	State now,next; // 定義2個狀態,當前和下一個
	st.Step_Counter=0; // 計數器清零
	q.push(st); // 入隊	
	vis[st.x]=1; // 訪問標記
	while(!q.empty())
	{
		now=q.front(); // 取隊首元素進行擴展
		if(now.x==m) // 出現目標態,此時爲Step_Counter 的最小值,可以退出即可
		{
			printf("%d\n",now.Step_Counter);
			return;
		}
		//按照規則搜索下一組數
		for(int i=1;i<=9;i+=2)
		{
			int s=now.x/10*10+i;
			if(CheckState(s,now))
			{
				vis[s]=1;
				next.x=s;
				next.Step_Counter=now.Step_Counter+1;
				q.push(next); 
			}
		}
		for(int i=0;i<=9;i++)
		{
			int s = now.x / 100 * 100 + i * 10 + now.x % 10;
			if(CheckState(s,now))
			{
				vis[s]=1;
				next.x=s;
				next.Step_Counter=now.Step_Counter+1;
				q.push(next);
			}
		}
		for(int i=0;i<=9;i++)
		{
			int s=now.x/1000*1000+i*100+now.x%100;
			if(CheckState(s,now))
			{
				vis[s]=1;
				next.x=s;
				next.Step_Counter=now.Step_Counter+1;
				q.push(next);
			}
		}
		for(int i=1;i<=9;i++)
		{
			int s=i*1000+now.x%1000;
			if(CheckState(s,now))
			{
				vis[s]=1;
				next.x=s;
				next.Step_Counter=now.Step_Counter+1;
				q.push(next); 
			} 
		}
		q.pop(); // 隊首元素出隊
	}
	printf("Impossible\n");
 	return;
}

int main()
{

	solvePrime();
	int t;
	scanf("%d",&t);
		for(int i=0;i<t;i++)
		{
//			printf("%d  %d\n",t,n);
//			while(!q.empty()) q.pop();
			memset(vis,0,sizeof(vis));
			scanf("%d%d",&n,&m);
			struct State tmp;
			tmp.x=n;
			tmp.Step_Counter=0;
			bfs(tmp);
		}
	return 0;
}


【收穫與反思】

題目本身沒什麼問題,兩次TLE都是因爲輸入輸出格式的問題最後一直等待輸入,需要再細節一點。

對於素數篩知識的補充。

1.傳統方法:根據是否有大於1小於本身的因子來判斷,複雜度爲O(nlognlogn)雖然還不會證明。

2.素數篩方法

 2.1埃式篩法,即埃拉託斯特尼篩法,本題採用。原理就是排除0,1,從2開始當前未篩去的數最小的數即爲素數(沒有比他小的數把他篩去),並把他的倍數一一篩去,然後檢索下一個未篩去的最小的數,如此循環。算法實現中注意j=i*i,能再提高下效率,反證法,假設有n能表示爲i*k,k爲一比i小的數,則在i=k時,n已經作爲k的倍數被篩去,不該存在,矛盾。


改進前後兩次的時間對比顯而易見。

另外還有線性的歐拉篩法,有待學習。



發佈了40 篇原創文章 · 獲贊 1 · 訪問量 6264
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章