これは自力では解けなかった。
http://indeednow-finala-open.contest.atcoder.jp/tasks/indeednow_2015_finala_f
問題
X人の人がおり、それぞれS,N,K社の3つの会社への就職希望の有無を尋ねた。
3つの会社にはそれぞれS人・N人・K人まで就職できる。
各人の就職希望は(2^3)通りあるので、X人全体では就職希望の組み合わせは(2^3X)通りある。
各人を3社のどれかに就職させるとき、全員の希望を満たせる組み合わせは何通りか。
解法
自力では解けなかったのでEditorialを参考にして解いた。
http://www.slideshare.net/chokudai/indeednow-finala
ホールの定理を使いつつ、半分全列挙風に7つの変数の範囲を定めていく。
何処も希望しない人がいる場合は当然全員の希望は満たせない。
よって、X人を以下の7通りに分類するとする。
- : それぞれS,N,K社のみに就職を希望する人の人数
- : それぞれSとN,NとK,KとSの2社に就職を希望する人の人数
- : 3社どれでも就職を希望する人の人数
これらの計X人を3社に振り分けることになる。
ここで各人の希望と会社の就職のマッチング関係を最大フローで表すと、ホールの定理より以下の条件を全て満たせば全員の希望を満たす組みあわせがある。
さすがに7変数でこの不等式を解くのは難しい。
そこで4変数分は総当たりし、残り3変数分は前処理で解くことを考える。
の4変数は総当たりするとすると、上記不等式でこれらは定数と考えられるので、残された変数に対し以下の関係が成り立つ。
上記式より、に対して条件を満たすの数の組み合わせ数を求めることができる。
に関する式が不等式でなく等式である場合、の数の組み合わせ数はが成り立つ。
後はこのを累積和の要領で足していけば、を求めることができる。
最後に、を総当たりしつつの総和を求めればよい。
X,S,N,Kは150以下だが、は300まで行くので注意。
int X,S,N,K; ll mo=1000000007; ll fact[200],factr[200]; ll dp[355][355][200]; ll ret; const int CN=401; ll C[CN][CN]; ll modpow(ll a, ll n = mo-2) { ll r=1; while(n) r=r*((n%2)?a:1)%mo,a=a*a%mo,n>>=1; return r; } void solve() { int i,j,k,l,r,x,y; string s; int Nx,Ny,Nz,Nxy; FOR(i,CN) for(j=0;j<=i;j++) C[i][j]=(j==0||j==i)?1:(C[i-1][j-1]+C[i-1][j])%mo; cin>>X>>S>>N>>K; fact[0]=factr[0]=1; FOR(i,199) fact[i+1]=fact[i]*(i+1)%mo, factr[i+1]=modpow(fact[i+1]); for(i=0;i<=X;i++) { FOR(x,301) FOR(y,301) { ll& t=dp[x][y][i]; if(i-x-y>=0) t = fact[i]*factr[x]%mo*factr[y]%mo*factr[i-x-y]%mo; if(x) t += dp[x-1][y][i]; if(y) t += dp[x][y-1][i]; if(x&&y) t -= dp[x-1][y-1][i]; if(t<0) t+= mo; while(t>=mo) t-= mo; } } if(S+N+K>=X) { FOR(Nx,min(X,S)+1) FOR(Ny,min(X-Nx,N)+1) FOR(Nz,min(X-Nx-Ny,K)+1) FOR(Nxy,X-Nx-Ny-Nz+1) { if(Nx+Ny+Nxy>S+N) break; ll pat=C[X][Nx]*C[X-Nx][Ny]%mo*C[X-Nx-Ny][Nz]%mo*C[X-Nx-Ny-Nz][Nxy]%mo; ret += pat * dp[N+K-Ny-Nz][S+K-Nx-Nz][X-Nx-Ny-Nz-Nxy] % mo; } } cout<<ret%mo<<endl; }
まとめ
ホールの定理なんて知らなかったし、知ってても7変数を半々で処理していくなんて思いつかないわ。