- 问题描述
已知一个四位的素数,要求每次修改其中的一位,并且要保证修改的结果还是一个素数,还不能出现前导零。你要找到一个修改数最少的方案,得到我们所需要的素数。
例如把1033变到8179,这里是一个最短的方案:
1033
1733
3733
3739
3779
8779
8179
修改了6次。
- 思路:
素数化点建图,采用迪杰斯特拉寻找最短路
- 代码
-
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<algorithm> #include<queue> #include<cstring> using namespace std; #define N (int)10000 typedef pair<int,int> PII; const int maxn = 1e5+10; int n,m,prime[maxn],cnt,st,en; int e[N], ne[N], idx,h[N],dis[N]; bool a[maxn]; bool judge(int a,int b) {//判断两个数字是否仅相差一位不同 int t = 0; for (int i = 1; i <= 1000; i*=10) { if (a % 10 != b % 10) { t++; if (t >= 2) { return false; } } a /= 10; b /= 10; } return true; } void add(int num1,int num2) {//数组模拟链表,邻接表的创建 e[idx] = num2; ne[idx] = h[num1]; h[num1] = idx++; } void init() { a[1] = 1; for (int i = 2; i<=maxn; i++) { if (!a[i]) { prime[cnt++] = i; if (!st&&i>1000) {//得到第一个大于1000的素数的位置st st = cnt - 1; } if (!en&&i > 9999) {//得到9999以内最后一个素数 en = cnt - 2; } } for (int j = 0; prime[j] <= maxn / i;j++) {//欧拉线性素数筛法 a[i*prime[j]] = true; if (i%prime[j] == 0) { break; } } } } int dijkastra() {//利用基于堆优化的dijkastra算法寻找最短路 memset(a, 0, sizeof(a)); memset(dis, 0x3f, sizeof(dis)); dis[n] = 0; priority_queue<PII, vector<PII>, greater<PII>>heap;//建立小根堆 heap.push({ 0,n });//起点到起点的距离为0 while (heap.size()) { auto t = heap.top();//取出堆顶元素,即此时到起点最短的点 heap.pop(); int ver = t.second, distance = t.first;//堆元素是pair,第一个元素是距离,第二个是节点序号 if (a[ver])continue;//已经被访问过,取消此次循环 a[ver] = true; for (int i = h[ver]; i != -1; i = ne[i]) {//遍历当前该点所有的邻接点,更新距离 int j = e[i]; if (dis[j] > dis[ver] + 1) {//需要更新距离 dis[j] = dis[ver] + 1; heap.push({ dis[j],j });//放进堆里面 } } } if (dis[m] == 0x3f3f3f3f)return -1;//如果目标终点的距离仍为无穷大,则认为终点与起点之间不可达 return dis[m]; } int main() { init();//初始化素数数组 memset(h, -1, sizeof(h));//邻接表头节点数组均填入-1 //printf("%d", judge(1001,1002)); //printf("%d %d ", prime[st],prime[en]); scanf("%d %d", &n, &m);//n为起点,m为终点 for (int i = st; i <= en; i++) {//遍历所有1000到9999之间的素数 for (int j = i + 1; j <= en; j++) { if (judge(prime[i], prime[j])) {//i和j之间仅存在一位不同 add(prime[i], prime[j]);//添加相应的边 add(prime[j], prime[i]); } } } cout << dijkastra() << endl;
- 总结
2.最开始还是想着用枚举遍历的方法解决,后来感觉时间复杂度过大,于是采用将每一个素数看作一个点,利用寻找最短路的算法。这里采用了时间复杂度最低的堆优化的迪杰斯特拉算法。其中由于判断素数开了一个bool数组,最短路算法判断节点是否被访问也要一个bool数组,防止内存超限,直接将a数组用完重新赋值false。最开始建图的时候,邻接表总是出问题,一个点后面总是后面跟着错误的点,怎么都debug不出来,后来才发现是邻接表的数组开小了。
3.开数组一定要先判断范围,否则到最后调试也很难发现,血的教训。