Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 24280 | Accepted: 13417 |
Description
— 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.
1033The 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.
1733
3733
3739
3779
8779
8179
Input
Output
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的倍數被篩去,不該存在,矛盾。
改進前後兩次的時間對比顯而易見。
另外還有線性的歐拉篩法,有待學習。