Official
E - チーム編成 / Team Formation Editorial
by
E - チーム編成 / Team Formation Editorial
by
physics0523
Editorial for printing
\(N\) 人の中から \(K\) 人を選び取る方法の数は二項係数 \(C(N,K)\) で表されます。
制約内では \(C(30,15) < 1.6 \times 10^8\) が取りうる最大値なので答えがこの値を超えることはありません。なので、方法の数を \(\bmod{10^9+7}\) で割った余りを取る必要はありません。
\(C(30,15)\) 通りを全探索できればよいのですが、これに \(N\) を掛けた時間計算量 \(O(N \times C(N,K))\) では実行時間制限に間に合いません。
このような場合、 半分全列挙 という手法で全探索を効率化することができることがあります。本問題に沿った形で説明します。
- 人を均等な大きさの \(2\) つの集合 \(X,Y\) に分ける。
- \(X,Y\) それぞれに対し、各人を採用するかしないかの \(2^{|X|}\) および \(2^{|Y|}\) 通りを全探索する。
- 最後に、 \(X,Y\) の適切な組み合わせを全探索する。
各集合に対して、以下を計算します。 ( \(2^{|X|}\) および \(2^{|Y|}\) 通りの全探索です )
- \((\) 選んだ人数 \(k,\) 実力値の総和を \(M\) で割った余り \(s\) \()\) に対する、この条件を満たす人の選び方の方法の数
すると、 \(X\) 側の \((k,s)\) に対し、 \(Y\) 側の \((K-k,(M-s) \bmod{M})\) を組み合わせると問題文中の条件を満たす選び方を抽出できます。
半分全列挙のミソは、愚直に全探索すると時間計算量 \(O(2^{|S|})\) かかる探索を、全探索であるという性質を保ちながら時間計算量 \(O(2^{|S|/2})\) に落とすことができることです。
実装例 (C++):
#include<bits/stdc++.h>
using namespace std;
using pi=pair<int,int>;
map<pi,int> work(int M,vector<int> &A){
int N=A.size();
map<pi,int> res;
for(int i=0;i<(1<<N);i++){
pi p={0,0};
for(int j=0;j<N;j++){
if(i&(1<<j)){
p.first++;
p.second+=A[j];
p.second%=M;
}
}
res[p]++;
}
return res;
}
int main(){
int N,K,M;
cin >> N >> K >> M;
vector<int> X,Y;
for(int i=0;i<N;i++){
int A;
cin >> A;
if(i&1){Y.push_back(A);}
else{X.push_back(A);}
}
auto lef=work(M,X);
auto rig=work(M,Y);
int res=0;
for(auto &nx : lef){
pi target={K-nx.first.first,(M-nx.first.second)%M};
auto it=rig.find(target);
if(it!=rig.end()){
res+=nx.second*(*it).second;
}
}
cout << res << "\n";
return 0;
}
posted:
last update:
