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;
}
投稿日時:
最終更新:
