AtCoder Beginner Contest 188 F

题目描述

给你两个数 (x),(y)

可以对 (x) 进行 (+1,-1)( imes 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-m);
		return 0;
	}
	printf("%lld
",dfs(m));
	return 0;
}
原文地址:https://www.cnblogs.com/liuchanglc/p/14260518.html