题目描述
给你两个数 \(x\),\(y\)
可以对 \(x\) 进行 \(+1,-1\) 或 \(\times 2\) 的操作
问最少操作多少次后变为 \(y\)
\(x,y \leq 10^{18}\)
分析
将问题转化为从 \(y\) 变为 \(x\),可以进行 \(+1,-1\) 或 \(\div 2\) 的操作
之所以这样做,是因为转化题意之后操作更受约束
如果当前值为偶数,则只能除以 \(2\)
如果当前值为奇数,则只能执行加 \(1\) 或减 \(1\) ,再除以 \(2\)
因为如果要加更多的数,完全可以在除以 \(2\) 之后再加
相比之下,你可以随时加 \(1\) 或乘 \(2\),所以有更多的分支
如果你把它想象成一个根树遍历,那就好像如何重复地找到父对象要比探索所有子对象简单得多
这样做复杂度看起来还是 \(2^{logy}=y\)
实际上,若 \(y\) 为偶数,只会有 \(y/2\) 一个分支
若 \(y\) 为奇数,会有 \((y+1)/2\) 和 \((y-1)/2\) 两个分支
而且这两个数是相邻的,设为 \(a,a+1\)
如果 \(a\) 为奇数,下一层递归会有 \((a+1)/2,(a-1)/2,(a+1)/2\) 三个分支
如果我们采用记忆化搜索的方式,只会递归到两个分支
同样若 \(a\) 为偶数,下一层递归会有 \(a/2,a/2,(a+2)/2\) 三个分支
记忆化后也变为了两个分支
因为每一次都会除以 \(2\),所以最多递归 \(log\) 层
而每一层只有两个不同的数,所以最终的状态是 \(log\) 级别的
代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<map>
#define rg register
inline int read(){
rg int x=0,fh=1;
rg char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
long long n,m;
std::map<long long,long long>mp;
long long dfs(rg long long now){
if(mp.find(now)!=mp.end()) return mp[now];
if(now<=n) return n-now;
rg long long nans=now-n;
if(now&1){
nans=std::min(nans,std::min(dfs((now-1)/2),dfs((now+1)/2))+2);
} else {
nans=std::min(nans,dfs(now/2)+1);
}
return mp[now]=nans;
}
int main(){
scanf("%lld%lld",&n,&m);
if(n>=m){
printf("%lld\n",n-m);
return 0;
}
printf("%lld\n",dfs(m));
return 0;
}