公式

N - ソフトウェアアップデート/Software Update 解説 by mechanicalpenciI


問題の入力が与えられたとき、\(\lvert f(t)-S\rvert\) の期待値はソフトウェアのアップデート順によってのみ決まり、
さらにその期待値は \(\frac{1}{S}\int_0^S \lvert f(t)-S\rvert dt\) によって計算されるため、 \(\int_0^S \lvert f(t)-S\rvert dt\) を最小化する事を考えれば良いです。

この問題を、\(N\) 個のソフトウェアの集合の部分集合 \(\mathcal{ S}\) (ただし空集合を除く)について、(小さい方から)順に解いていくことを考えます。ただし、式中の \(S\) の値は集合 \(\mathcal{S}\) によらず、\(S=\displaystyle\sum_{i=1}^N T_i\) で定義し、すなわち、

\[ \int_0^{S'} \lvert f(t)-S\rvert dt (ただし、S=\displaystyle\sum_{i=1}^N T_i, S'=\displaystyle\sum_{i\in \mathcal{S}} T_i ) \]

の最小値(以下、これを\(F(\mathcal{S})\) とします)を求めていくことを考えます。

\(\lvert \mathcal{S}\rvert =1\)、すなわちアップデートされるソフトウェアが \(1\) つの場合は、順番は \(1\) 通りしかありえず、\(\mathcal{S}\) がソフトウェア \(i\) のみからなる場合、

\[ F(\mathcal{S})=\int_0^{T_i} \lvert 2Nt-S\rvert dt \]

となります。

次に、 \(\lvert \mathcal{S}\rvert \geq 2\) の場合について、\(\mathcal{S}\) 自身と空集合を除く\(\mathcal{S}\) の部分集合についての答えがわかっているとして、\(\mathcal{S}\) に対する答えを求めることを考えます。\(\mathcal{S}\) に属するソフトウェアをアップデートする順番の中で、(\(\mathcal{S}\) に属する)あるソフトウェア \(i\) を最後にアップデートするようなものについての最小値は、\(\mathcal{S}'\)\(\mathcal{S}\)からソフトウェア \(i\) を除いた集合として、

\[ F(\mathcal{S}')+\int_{S'-T_i}^{S'}\left\lvert \frac{2N}{2\lvert \mathcal{S}\rvert-1}t-S \right\rvert dt \]

となります。よって、\(F(\mathcal{S})\) の値は \(\mathcal{S}\) に属するソフトウェア \(i\) それぞれについてのこの値の最小値となります。

あとはこれを順に計算していけば良いです。具体的には、例えば、\(X=0,1,\ldots, 2^N-1\) について \(\mathcal{S}(X)\) を、\(X\)\(i\) ビット目が \(1\) の時かつその時に限り、 \(\mathcal{S}(X)\)はソフトウェア \(i\) を含むようなものとして定義し、\(\mathcal{S}(0),\mathcal{S}(1), \ldots, \mathcal{S}(2^N-1)\) について順に計算すれば良いです。

ここで、途中の計算で登場する積分式はすべて\(\int_a^b \lvert ct-S\rvert dt\) の形で書かれているため、\(ac\), \(bc\)\(S\) の大小関係で場合分けする必要がありますが、いずれの場合も答えを \(O(1)\) で求める事ができます。 よって、各集合に対する答えを \(O(N)\) で求める事ができ、全体でこの問題を \(O(N\cdot 2^N)\) で解く事ができました。

c++ による実装例

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

long double calc(long double l, long double r,long double c, long double s){
	if(c*r<s){
		return (r-l)*(s-c*(r+l)/2.0);
	}
	if(s<c*l){
		return (r-l)*(c*(r+l)/2.0-s);
	}
	return (s-c*l)*(s/c-l)/2.0+(c*r-s)*(r-s/c)/2.0;
}

int main(void) {
	int n;
	cin>>n;
	vector<long double> t(n);
	vector<int> cnt(1<<n);
	vector<long double> s(1<<n);
	vector<long double> ans(1<<n,1e+22);
	for(int i=0;i<n;i++){
		cin>>t[i];
		s[(1<<n)-1]+=t[i];
	}
	ans[0]=0;
	for(int i=0;i<(1<<n);i++){
		for(int j=0;j<n;j++){
			if(!(i&(1<<j))){
				cnt[i+(1<<j)]=cnt[i]+1;
				s[i+(1<<j)]=s[i]+t[j];
				ans[i+(1<<j)]=min(ans[i+(1<<j)],ans[i]+calc(s[i],s[i+(1<<j)],((long double)2.0*n)/(2*cnt[i+(1<<j)]-1),s[(1<<n)-1]));
			}
		}
	}
	cout<<fixed<<setprecision(10)<<(ans[(1<<n)-1]/s[(1<<n)-1])<<endl;
	
	return 0;
}

投稿日時:
最終更新: