当青训营遇上码上掘金
之主题 3:寻友之旅
本文章是对“「青训营 X 码上掘金」主题创作活动”的第三题:寻友之旅的讲解,思路来源于作者本人,若有问题欢迎指出。
题目分析
首先,我们可以简单的分析一下小青的行动方法:
- 走路:使从x位置转移到x+1位置或者x-1位置。
- 坐公交:使从x位置转移到x*2位置。
位置范围是0~100000。
这两种方法的每次行动耗费时间都是1分钟。值得一提的是,坐公交不能坐回去,然而,这个条件也并没有什么用——因为乘法不像减法一样会使数字变小嘛!
解题主要思路
bfs
天不生我暴搜,万古算法如长夜!只要稍微接触学过算法的同学,看到此题的第一印象就是暴搜。
然而,毋庸置疑的是,如果不限制范围,那么小青每次从每个位置可以到达的新位置都是3个,这使得考虑情况会以指数级增长——然后爆栈...
但是,机智的我们发现了,由于位置范围是给定的:0~100000,所以我们直接限定位置范围,然后让每个位置标记一下最先到的地方,下次就不要再到一次这个地方了,毕竟最先到的肯定有最优的最小时间嘛!这样,时间复杂度就降到了惊人的O(m),也就是O(1e5)!
看起来,本题的完美解法就诞生了。
dijkstra或spfa
众所周知,spfa死了spfa只适用于随机数据情况,其他情况下效率更差,所以本题不讨论它。
那么,dijkstra在本题效果怎么样?本人无意间看到,有许多同学使用这个方法解答这道题,然而,哪怕是用上了堆优化,本题的时间复杂度也会达到O(mlog(m)),也就是大约1e6的级别,太烂辣!
作者本人的小优化
由于,我们可以观察到,小青从起点走到终点的行动路线具有对称性。因此,虽然规定了小码不能移动,但是我们仍然可以设置让小码、小青同时行动,以加快bfs搜索的效率,减少废枝判断。
是的,这也就是ACM中的双向BFS搜索优化。
完整代码如下:
#include <bits/stdc++.h>
#define fi first
#define se second
#define endl '\n'
#define butane cout<<"test"<<endl;
#define lowbit(x) x&-x
using namespace std;
const int maxv=1e6+100;
const int mod=2008;
const int maxNum=0x7fffffff-10;
const double eps=1e-8;
const double PI=3.1415926535;
typedef long long LL;
inline int read(){
int x=0, f=1;
char ch=getchar();
while (ch < '0' || ch > '9'){
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9'){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
// return x;
}
inline void write(__int128 x){
if (!x) putchar('0');
char F[200];
__int128 tmp = x > 0 ? x : -x;
if (x < 0) putchar('-');
int cnt = 0;
while (tmp > 0) F[cnt++] = tmp % 10 + '0',tmp /= 10;
while (cnt > 0) putchar(F[--cnt]);
// putchar('\n');
// putchar(' ')
}
//双向广度优先搜索
//理论上复杂度更低,为O(1e5)
//此题广度搜索比dijkstra或spfa更优秀
//应该有数论解法
bool visS[maxv],visT[maxv];
int cntS[maxv],cntT[maxv];
bool judge(int x){return x>=0&&x<=100000;}
int bfs(int s,int t){
int ans=maxNum;
queue<pair<int,int> > qs,qt;
qs.push({s,0}),qt.push({t,0});
while(qs.size()||qt.size()){
if(qs.size()){
int sTop=qs.front().first,sTopCnt=qs.front().second;qs.pop();
visS[sTop]=1;
if(judge(sTop-1)&&!visS[sTop-1]){
if(visT[sTop-1]) ans=min(ans,sTopCnt+cntT[sTop-1]);
qs.push({sTop-1,cntS[sTop-1]=sTopCnt+1}),visS[sTop-1]=1;
}
if(judge(sTop+1)&&!visS[sTop+1]){
if(visT[sTop+1]) ans=min(ans,sTopCnt+cntT[sTop+1]);
qs.push({sTop+1,cntS[sTop+1]=sTopCnt+1}),visS[sTop+1]=1;
}
if(judge(sTop*2)&&!visS[sTop*2]){
if(visT[sTop*2]) ans=min(ans,sTopCnt+cntT[sTop*2]);
qs.push({sTop*2,cntS[sTop*2]=sTopCnt+1}),visS[sTop*2]=1;
}
}
if(qt.size()){
int tTop=qt.front().first,tTopCnt=qt.front().second;qt.pop();
visT[tTop]=1;
if(judge(tTop-1)&&!visT[tTop-1]){
if(visS[tTop-1]) ans=min(ans,tTopCnt+cntS[tTop-1]);
qt.push({tTop-1,cntT[tTop-1]=tTopCnt+1}),visT[tTop-1]=1;
}
if(judge(tTop+1)&&!visT[tTop+1]){
if(visS[tTop+1]) ans=min(ans,tTopCnt+cntS[tTop+1]);
qt.push({tTop+1,cntT[tTop+1]=tTopCnt+1}),visT[tTop+1]=1;
}
if(tTop%2==0&&judge(tTop/2)&&!visT[tTop/2]){
if(visS[tTop/2]) ans=min(ans,tTopCnt+cntS[tTop/2]);
qt.push({tTop/2,cntT[tTop/2]=tTopCnt+1}),visT[tTop/2]=1;
}
}
}
return ans;
}
signed main(){
int n=read(),k=read();
write(bfs(n,k));
return 0;
}