前提知識
解説
幅優先探索の容量でやる
幅優先探索を知らない場合はこれを参照。
dist[i]=i個目のマスに行くときの最短手数。その段階で行けないときは-1。
遷移方法
iの__builtin_popcountをeと置く。
①$dist[i+e]=dist[i]+1(i+e\leqq n)$
②$dist[i-e]=dist[i]+1(i-e>0)$
こんな感じでやれば行ける。
一番最初に更新された数字が最短距離なので2回以上の更新は不要
またqueueには高々$N$回しかpushされず、__builtin_popcountの計算量は$O(1)$。よって実行時間は$O(N)$
C++での解答例
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;cin>>n;
//dist[i]=移動手数
//現段階で行けないときは-1
vector<int> dist(n+1,-1);
dist[1]=1;
queue<int> que;
que.push(1);
while(!que.empty()){
int u=que.front();que.pop();
//ここからは遷移
//__builtin_popcount(u)=uの二進数の桁が1になっている数=__builtin_popcount
//uから1手で行けるのはu+__builtin_popcountかu-__builtin_popcount
if(u+__builtin_popcount(u)<=n&&dist[u+__builtin_popcount(u)]==-1){
dist[u+__builtin_popcount(u)]=dist[u]+1;
que.push(u+__builtin_popcount(u));
}
if(u-__builtin_popcount(u)>0&&dist[u-__builtin_popcount(u)]==-1){
dist[u-__builtin_popcount(u)]=dist[u]+1;
que.push(u-__builtin_popcount(u));
}
}
cout<<dist[n]<<endl;
}