実装より考察が難しいタイプの問題。
https://yukicoder.me/problems/no/2287
問題
整数X,Y,Aが与えられる。Xに以下の処理を繰り返しYにしたい。
必要な最小処理回数を求めよ。
- Xをインクリメントする
- Xが正の場合、デクリメントする
- XにAを掛ける
- XをAで割った整数部分にする
解法
以下の2点が重要。
- 割り算は最初にしか行わなくてよい
- その後、掛け算を行うタイミングは、Xがfloor(Y/(A^k))またはfloor(Y/(A^k))+1の時だけ
そこで、後者の各状態に対しダイクストラ法を行うことを考える。
まずXをAで割りながら、floor(Y/(A^k))またはfloor(Y/(A^k))+1との差を見て、それらの状態に至る最小処理回数を求めよう。
あとは、floor(Y/(A^k))またはfloor(Y/(A^k))+1から周辺の状態に至る処理回数をダイクストラ法の要領で最小化していく。
int T; ll X,Y,A; ll V[61]; ll D[61][2]; priority_queue<pair<ll,int>> Q; void update(int tar,int add,ll v) { if(D[tar][add]>v) { D[tar][add]=v; Q.push({-v,tar*2+add}); } } void solve() { int i,j,k,l,r,x,y; string s; cin>>T; while(T--) { cin>>X>>Y>>A; V[0]=Y; int N=0; while(V[N]) { V[N+1]=V[N]/A; N++; } FOR(i,N+1) D[i][0]=D[i][1]=1LL<<60; x=0; while(1) { FOR(i,N+1) { D[i][0]=min(D[i][0],x+abs(V[i]-X)); D[i][1]=min(D[i][1],x+abs(V[i]+1-X)); } if(X==0) break; X/=A; x++; } while(Q.size()) Q.pop(); FOR(i,N+1) { Q.push({-D[i][0],i*2}); Q.push({-D[i][1],i*2+1}); } while(Q.size()) { int cur=Q.top().second/2; int add=Q.top().second%2; ll co=-Q.top().first; Q.pop(); if(D[cur][add]!=co) continue; if(add==0) { if(cur) update(cur-1,0,co+1+V[cur-1]-V[cur]*A); update(cur,1,co+1); } else { if(cur) update(cur-1,1,co+1+abs(V[cur-1]+1-(V[cur]+1)*A)); if(cur>=2) update(cur-2,0,co+1+abs(V[cur-2]-(V[cur]+1)*A)); if(cur) update(cur-1,0,co+abs(V[cur-1]-(V[cur]+1))); update(cur,0,co+1); } } cout<<D[0][0]<<endl; } }
まとめ
こういうの自信もって「これで正解できる」って言えないなぁ…。