1. 程式人生 > >UVALive 7143 Room Assignment(組合數學+DP)

UVALive 7143 Room Assignment(組合數學+DP)

case 逆元 cstring amp sig https spa gin print

題目鏈接

參考自:http://www.cnblogs.com/oyking/p/4508260.html

題意

n個人,其中有k對雙胞胎.現有m間房間,每間房間有容量ci問分配房間的方案數。

分析

設dp[i][j]為已經放滿了第i個房間之後,所剩下的雙胞胎的對數還有j對,然後對於i+1間房,我們可以從剩余的j對中選擇出a對,每對雙胞胎只是放一個就好,然後又從j-a對雙胞胎中選b對全部放進去,然後再從剩余的sum-j2中選擇c[i]-a-2b個放進i+1個房間裏面,這樣的話就能得到轉移方程,dp[i+1][j-a-b]=dp[i][j]C(j,a)C(j-a,b)C(sum-j2,c[i]-a-b2)

;然後組合數就用逆元去算就ok了。時間復雜度是O((mk)³)

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;

const int MAXN = 100010;
const int MOD = 1e9 + 7;

int inv[MAXN], fact[MAXN];

int _inv(int x) {
    if
(x == 1) return 1; return LL(MOD - MOD / x) * _inv(MOD % x) % MOD; } void init(int n = 100000) { fact[0] = 1; for(int i = 1; i <= n; ++i) fact[i] = fact[i - 1] * LL(i) % MOD; for(int i = 0; i <= n; ++i) inv[i] = _inv(fact[i]); } LL comb(int a, int b) {
if(a < b) return 0; return LL(fact[a]) * inv[b] % MOD * LL(inv[a - b]) % MOD; } int dp[13][111]; int c[13]; int T, n, m, k; int mulmul(LL a, LL b, LL c, LL d) { return a * b % MOD * c % MOD * d % MOD; } void update_add(int &a, int b) { a += b; if(a >= MOD) a -= MOD; } int solve() { memset(dp, 0, sizeof(dp)); dp[0][k] = 1; for(int i = 0, sum = n; i < m; ++i) { for(int r = 0; r <= k; ++r) if(dp[i][r]) { if(sum < 2 * r) break; for(int a = 0; a <= r; ++a) { for(int b = 0; b + a <= r; ++b) { if(c[i + 1] - a - 2 * b < 0) break; int t = mulmul(dp[i][r], comb(r, a), comb(r - a, b), comb(sum - 2 * r, c[i + 1] - a - 2 * b)); update_add(dp[i + 1][r - a - b], t); } if(i == m - 1) break; /// if i = m - 1 then a must be zero } } sum -= c[i + 1]; } return dp[m][0]; } int main() { init(); //printf("%d\n", comb(10, 1)); scanf("%d", &T); for(int t = 1; t <= T; ++t) { scanf("%d%d%d", &n, &m, &k); for(int i = 1; i <= m; ++i) scanf("%d", &c[i]); printf("Case #%d: %d\n", t, solve()); } }

UVALive 7143 Room Assignment(組合數學+DP)