E - 合計得点/Total Score 解説
by
physics0523
解法1: 全探索する
何らかの方法で全探索を実装することで、この問題に正解できます。
今回は bit全探索 の実装例を示します。
実装例 (C++):
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,k,res=0;
cin >> n >> k;
vector<int> a(n);
for(auto &nx : a){cin >> nx;}
for(int i=0;i<(1<<n);i++){
int solve=0,sum=0;
for(int j=0;j<n;j++){
if(i&(1<<j)){solve++;sum+=a[j];}
}
if(solve == k){res+=sum;}
}
cout << res << "\n";
return 0;
}
解法2: 全探索なしで上手く合計点を求める
少々発展的な解法となりますが、全探索を回避してこの問題を解いてみましょう。
ありうる全ての問題の選び方のうち、各問題が何度選ばれるかを考えます。
例: \(5\) 問中から \(3\) 問選ぶ場合
まずは問題 \(1\) が選ばれる回数を数えます。
この回数は、残りの \(4\) 問中から \(2\) 問選ぶ場合の数と一致します。(残りの \(4\) 問から \(2\) 問選んで、それに加えて問題 \(1\) を選べば、 問題 \(1\) を必ず含めて \(5\) 問中 \(3\) 問を選ぶ全通りを見ることができます。 )
他の問題 \(2,3,4,5\) についても同じことが言えます。
つまり、全ての問題について各問題が選ばれる回数は \(4\) 問中 \(2\) 問を選ぶ場合の数に一致します。
ここで、二項係数という概念を導入します。
\(n\) 個の区別可能なモノの中から \(r\) 個を選ぶ場合の数を \(_nC_r, C(n,r), \binom{n}{r}\) などと表記します(これらは全て同じ意味です)。
これは、 \(C(n,r) = \frac{n \times (n-1) \times \dots \times (n-r+1)}{1 \times 2 \times \dots \times r}\) と計算できます。
但し \(n=r\) または \(r=0\) のとき \(C(n,r)=1\) 、 \(n<r\) のとき \(C(n,r)=0\) です。
先程の問題の例では、全ての問題の配点の合計に \(C(4,2)\) を掛ければ答えを求めることが出来ます。
一般に \(N\) 問の中から \(K\) 問を選んで元の問題を解く場合、特定の1問を必ず選ぶようにした場合の数は \(C(N-1,K-1)\) となり、答えは (全ての問題の配点の合計) \(\times C(N-1,K-1)\) で求まります。
実装例(C++):
#include<bits/stdc++.h>
using namespace std;
int C(int n,int r){
if(n<r){return 0;}
int res=1;
for(int i=1;i<=r;i++){
res*=(n+1-i);
res/=i; // res must be an integer
}
return res;
}
int main(){
int n,k,sum=0;
cin >> n >> k;
for(int i=0;i<n;i++){
int a;
cin >> a;
sum+=a;
}
cout << sum*C(n-1,k-1) << "\n";
return 0;
}
二項係数 \(C(n,r)\) を計算する際の補足:
二項係数はループを使えば求めることが出来ますが、先程示した計算式の分母と分子を別個に管理して最後に割るなどした場合、早期のオーバーフローを招く危険があります。
また、むやみに整数型で除算を行っても不正確な結果になる可能性があります。
実装例のように計算していくと、 \(C(n,i-1)\) の値から \(C(n,i)\) の値を求めていくことができ、この両者は整数であることが分かるので前者に \(n+1-i\) を掛けてから \(i\) で割れば、ここで生じる掛け算でオーバーフローしない範疇で正確な結果を得ることができます。
投稿日時:
最終更新: