Official

C - Merge the balls Editorial by mechanicalpenciI


それぞれの操作を順にシミュレーションすることを考えます。

ここで、\(N\) 個のボールの大きさはすべて \(2\) のべき乗であり、手順において取り除かれたボールの大きさが \(2\) のべき乗であるとき、新たに付け加えられるボールの大きさも \(2\) のべき乗となることから、登場するボールの大きさはすべて \(2\) のべき乗です。
また、\(X=Y\)\(2^X=2^Y\) が同値であることから、ボールの大きさの代わりにボールの大きさの(\(2\) を底とした)対数を管理すれば十分です。

具体的には列にあるボールの数 \(L\) と 列にあるボールの大きさの対数を左から順に並べたもの \(S=(S_1,S_2,\ldots,S_L)\) を管理しておき、これを更新していくことを考えます。
最初は\(L=0\), \(S=()\)(空の列)です。
\(i\) 個目のボールを列の一番右に付け加える操作は \(L\leftarrow (L+1)\) とし、\(S\) の末尾に \(A_i\) をつけ加えれば良いです。
その後の手順は、列の大きさが \(2\) 以上である限り、「\(S\) の末尾から \(2\) つの要素 \(S_{L-1}, S_{L}\)を取り出して大きさを比較し、異なれば元に戻して操作を終了し、等しければ(すなわち\(S_{L-1}=S_L=X\) であれば )、 \(L\leftarrow (L-1)\) として \(S\) の末尾に \((X+1)\) を加えて再度この手順を行う」ことを繰り返せば良いです。ここで、\(2^X+2^X=2^{X+1}\) であることに注意してください。\(N\) 回の操作が終了した後の \(L\) の値が答えとなります。

次に、計算量について考えます。前者の \(i\) 個目(\(1\leq i\leq N\))のボールを列の一番右に付け加える操作はちょうど \(N\) 回行われます。後者の手順について、「 \(2\) つのボールを取り出して \(1\) つを加える」という操作は列のボールの数を \(1\) 減少させるため、 \(N\) 回の操作の中で高々 \((N-1)\) 回しか行うことができません。よって、列にボールを取り出す・加えるという操作はそれぞれ高々 \(O(N)\) 回しか行われません。

それぞれの操作は配列で行うこともできますし、スタック等で管理することもできます。いずれの場合も末尾に付け加えたり取り除く操作は \(O(1)\) でおこなうことができます。よって、シミュレーションに必要な時間計算量は全体で \(O(N)\) であり十分高速です。よって、この問題を解くことができました。

c++ による実装例:

#include <bits/stdc++.h>
using namespace std;

int main() {
	int n,l=0;
	int a[200000];
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a[l];
		l++;
		while(l>1){
			if(a[l-2]==a[l-1]){
				a[l-2]++;
				l--;
			}
			else break;
		}
	}
	cout<<l<<endl;
	return 0;
}

posted:
last update: