公式

G - Highest Ratio 解説 by mechanicalpenciI


\(2\) 次元平面上に 点 \(0, 1,2,\ldots, N\)\(N+1\) 個の点があるようなものを考えます。
ここで、点 \(0\) の座標は \((x_0,y_0)=(0,0)\), 点 \(i\) \((1\leq i\leq N)\) の座標は \((x_i,y_i)=(i,A_1+A_2+\cdots +A_i)\) です。
このとき、\(A_L,A_{L+1},\ldots, A_R\) の平均は点 \({L-1}\) と点 \(R\) を結ぶ直線の傾きで表されます。

\(k\) \((1\leq k\leq N)\) を固定し、数列 \(A\)\(k\) 項目から \(r\) 項目 \((k\leq r\leq N)\) までの平均値としてあり得る最大値を求めることを考えます。
\({k-1}, k,k+1,\ldots, N\) の凸包を取ります。 点 \({k-1},k, k+1,\ldots, N\) が全て同一直線上に存在した時、\(A_k=A_{k+1}=\cdots~A_N\) であり、\(r\) の値によらず平均は \(A_k\) となります。そうでない時、凸包は有限な面積を持つ領域となります。\(x\) 座標が最小である唯一の点である 点 \({k-1}\) は凸包の境界上に存在します。点 \((k-1)\) から時計回りに境界を進んだ時に次に境界上に存在する点が点 \(p\) であったとき、問題の答えは\(\frac{y_p-y_{k-1}}{x_p-x_{k-1}}\) となります。なぜなら、点 \((k-1)\) から 時計回りに境界を進んだ時に次に境界上に存在する点が点 \(q\) であったとき、凸包に含まれる点 \(i\) \((k\leq i\leq N )\) と点 \(k-1\) を結んだ直線の傾きは、凸包の定義から(点 \(q\) と 点 \(k-1\) を結んだ直線の傾き)以上(点 \(p\) と 点 \(k-1\) を結んだ直線の傾き)以下となります。今求めたいのは点 \(i\) \((k\leq i\leq N )\) と点 \(k-1\) を結んだ直線の傾きの最大値と言い換える事ができるため、答えは点 \(p\) と 点 \(k-1\) を結んだ直線の傾きである \(\frac{y_p-y_{k-1}}{x_p-x_{k-1}}\) となります。

よって、結局それぞれの \(k\) について点 \({k-1}, k+1,\ldots, N\) の凸包を取った時に点 \((k-1)\) から時計回りに境界を進んだ時に次に境界上に存在する点, 言い換えれば上包を構成する点列において点 \((k-1)\) の隣の点が分かれば良いです。
それぞれの \(k\) について凸包を最初から求めようとすると実行時間の制約をオーバーしてしまいますが、 点 \(N,N-1,\ldots, 0\) の順に点を追加しながら上包を求めていく過程で、点 \({k-1}\) を追加した時点での上包をなす点列のうち、点 \(k-1\) とその \(1\) つ前の点を結んだ直線の傾きを求めていけば、点 \(0,1,\ldots,N\) からなる点集合の上包を\(1\) 回求めるのに必要な計算量と同等な計算量で解く事ができます。
点の座標を求めた時点で、すでに \(x\) 座標についてソートした状態で点の情報を得られるので、グラハムスキャン等のアルゴリズムを用いれば全体で \(O(N)\) で答えを求める事ができます。
よってこの問題を解く事ができました。

c++ による実装例:

#include <bits/stdc++.h>

using namespace std;

int main(void){
	int n;
	long double x,y;
	cin>>n;
	vector<long double>a(n+1,0.0);
	vector<long double>ans(n);
	vector<int>b;
	for(int i=0;i<n;i++){
		cin>>a[i+1];
		a[i+1]+=a[i];
	}
	int bsz=0;
	for(int i=n;i>=0;i--){
		while(b.size()>1){
			x=(a[b[bsz-1]]-a[i])*((long double)(b[bsz-2]-i));
			y=(a[b[bsz-2]]-a[i])*((long double)(b[bsz-1]-i));
			if(x<=y){
				b.pop_back();
				bsz--;
			}
			else break;
		}
		if(i<n)ans[i]=(a[b[bsz-1]]-a[i])/((long double)(b[bsz-1]-i));
		b.push_back(i);
		bsz++;
	}
	for(int i=0;i<n;i++)cout<< std::fixed << std::setprecision(10)<<ans[i]<<endl;
	return 0;
}

投稿日時:
最終更新: