Official

C - Manga Editorial by m_99


操作の方法の考察

問題文中では操作として \(2\) 冊売って代わりに \(1\) 冊買っていますが、売った時にどれを買うのかを保留できるものとしても問題ありません。
まず、\(2\) 冊以上持っている単行本は \(1\) 冊を残して売って良いです。次に、高橋君が持っている冊数は初めが \(N\) で、操作をすると減っていきます。そのため、\(N\) より大きな巻を読むことが出来ず、そのような巻も売って良いです。
本を買う時は、現在持っていない巻のうち最小のものを買うとして良いです。また、最終的に読むことになる巻を一度売って買い戻すのは好ましくありません。 以上を踏まえると、以下のようにするのが最適です。

  1. \(2\) 冊以上持っている巻や \(N\) より大きな巻を売る
  2. 単行本を買える場合は、現在持っていない巻のうち最小のものを買う。
  3. 単行本を買えない場合は、最終的に読むことにならない巻を売る。これは現在持っているうち最大の巻が第一候補である。ただし、その時点で操作をやめて読み始めた場合に最大の巻を読むことになるならば操作をやめる。

シミュレーション・計算量改善

\(vol_i\) を現在 \(i\) 巻を持っているかどうかを表す配列とします。読める巻数の最大値は \(N\) なので、配列の長さは \(N\) 前後で十分です(あるいは、平衡二分木を用いることで \(N\) を超える巻まで同様に扱っても構いません)。
これによって現在持っていない巻のうち最小のものや現在持っている巻のうち最大のものが分かるため、これまで売ってきた(かつ代わりに買うのに使っていない)単行本の冊数を管理しながら上述の戦略をシミュレーションすれば答えは求められますが、現在持っていない巻のうち最小のもの等を求めるのに毎回 \(\mathrm{O}(N)\) かけた場合、全体で\(\mathrm{O}(N^2)\) となります。これでは本問の実行時間制限に間に合いません。
現在持っていない巻のうち最小のものを \(L\)、現在持っている巻のうち最大のものを \(R\) とします。本を売ると \(R\) は減り、買うと \(L\) は増えます。操作によって変化したかもしれない、現在持っていない巻のうち最小のものを\(1,2,\ldots\) と調べる代わりに \(L, L+1,L+2,\ldots\) と調べると、全体で調べるのにかかるステップ数は操作回数と \(L\) が増えた回数の和となります。同様のことが \(R\) についても言えて、計算量が \(\mathrm{O}(N)\) となり、十分高速になります。

実装例(C++)

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

int main() {
    
	int N;
	cin>>N;
	
	vector<bool> vol(N+2,false);
	vector<int> a(N);
	
	int sold = 0;
	for(int i=0;i<N;i++){
		cin>>a[i];
		if(a[i]>=vol.size())sold++;
		else if(vol[a[i]])sold++;
		else vol[a[i]] = true;
	}
	
	int L = 1;
	int R = N+1;
	while(true){
		while(vol[L])L++;
		while(R!=0&&!vol[R])R--;
		if(sold>=2){
			sold-=2;
			vol[L] = true;
		}
		else{
			if(L>=R)break;
			vol[R] = false;
			sold++;
		}
	}
	
	cout<<L-1<<endl;
	
	return 0;
}

posted:
last update: