kmjp's blog

競技プログラミング参加記です

yukicoder : No.2287 ++ -- *=a /=a

実装より考察が難しいタイプの問題。
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;
		
	}
}

まとめ

こういうの自信もって「これで正解できる」って言えないなぁ…。