组合计数小练

组合数学什么的,最有趣了呢……

[51nod 1251] Fox序列的数量

题意

求满足以下条件的序列数目:

  1. 序列长度为 $ n $ ,每个元素都属于 $ [1,m] \cap Z $ ;
  2. 这个序列单调不降;
  3. 这个序列出现次数最多的数是唯一的。

数据范围: $ 1≤n,m≤100000 $ ,答案对 $ 1e9+7 $ 取模

题解

先枚举出现次数最多的数的出现次数 $ k $ ,我们要计算的是 $ x_1+x_2+...+x_{m-1}=n-k, ; x_i≤k-1 $ 的非负整数解数目。

可以枚举有多少个数 $ ≥k $ ,显然需要枚举的个数不超过 $ \lfloor \frac{n}{k} \rfloor $ ,先给这些数分配 $ k $ 的出现次数,再将剩下的次数随意分配,容斥一下就好了。

#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define LL long long
#define pii pair<int, int>
using namespace std;
const int N = 200005;
const int mod = 1e9 + 7;

template <typename T> void read(T &x) {
    int f = 0;
    register char c = getchar();
    while (c < '0' || c > '9') f |= (c == '-'), c = getchar();
    for (x = 0; c >= '0' && c <= '9'; c = getchar())
        x = (x << 3) + (x << 1) + (c ^ '0');
    if (f) x = -x;
}

inline int add(register int x, register int y) {
    static int z;
    return ((z = x + y) >= mod) ? (z - mod) : z;
}

inline int sub(register int x, register int y) {
    return (x < y) ? (x - y + mod) : (x - y);
}

inline void upd(int &x, register int y) {
    static int z;
    x = ((z = x + y) >= mod) ? (z - mod) : z;
}

LL fac[N], fav[N], inv[N];

inline LL C(int x, int y) {
    if (x < y || y < 0) return 0;
    return fac[x] * fav[y] % mod * fav[x - y] % mod;
} 

void init() {
    fac[0] = fav[0] = 1;
    inv[1] = fac[1] = fav[1] = 1;
    for (int i = 2; i < N; ++i) {
        inv[i] = -mod / i * inv[mod % i] % mod + mod;
        fac[i] = fac[i - 1] * i % mod;
        fav[i] = fav[i - 1] * inv[i] % mod;
    }
}

void solve() {
    static int n, m, f[N];
    read(n); read(m);
    if (m == 1) return (void) (puts("1"));
    if (n == 1) return (void) (printf("%d\n", m));
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        upd(ans, m * C(n - i + m - 2, m - 2) % mod);
        for (int j = 1; j <= m - 1 && i * (j + 1) <= n; ++j) {
            int del = m * C(m - 1, j) % mod * C(n - i * (j + 1) + m - 2, m - 2) % mod;
            if (j & 1) upd(ans, mod - del);
            else upd(ans, del);
        }
    }
    cout << ans << endl;
}

int main() {
    init();
    int T; read(T);
    while (T--) solve();
    return 0;
}

[51nod 1634] 刚体图

题意

在一个 $ n*m $ 的网格图上,在若干个格子中添加一条对角线(两个方向中的一种),使得不存在一种方法,能够在不改变所有边长的情况下改变图的形状。

数据范围: $ n,m≤10 $

题解

可以发现两个小性质:

1、对角线两个方向上加边对于固定图而言是等价的;

2、以下这张图不是刚体图:

3、不论如何折叠,同一行中经过的竖线和同一列经过的横线一定是互相平行的。

因此,加入一条对角线相当于强制一组横线和一组竖线垂直。

我们的目标是要让所有的横线和竖线垂直。

这就相当于有 $ n $ 个点 $ X $ 部和 $ m $ 个点 $ Y $ 部的联通二分图计数。

这个和无向连通图一样 $ DP $ 就好了。

#include<bits/stdc++.h>
using namespace std;
const int N = 11;
const int mod = 1e9 + 7;

int n, m;
int c[N][N], f[N][N];

inline void upd(int &x, register int y) {
    static int z;
    x = ((z = x + y) >= mod) ? (z - mod) : z;
}

int Qpow(int x, int p) {
    int ans = 1;
    while (p) {
        if (p & 1) ans = 1LL * ans * x % mod;
        x = 1LL * x * x % mod;
        p >>= 1;
    }
    return ans;
}

int dfs(int n, int m) {
    if (~f[n][m]) return f[n][m];
    if (n == 0) return (m <= 1);
    if (m == 0) return (n <= 1);
    if (n == 1 && m == 1) return 2;
    int ans = Qpow(3, n * m);
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= m; ++j) {
            if (i + j == n + m) continue;
            int del = 1LL * dfs(i, j) * Qpow(3, (n - i) * (m - j)) % mod;
            del = 1LL * del * c[n - 1][i - 1] % mod;
            del = 1LL * del * c[m][j] % mod;
            upd(ans, mod - del);
        }
    }
    return f[n][m] = ans;
}

int main() {
    c[0][0] = 1;
    for (int i = 1; i < N; ++i) {
        c[i][0] = 1;
        for (int j = 1; j <= i; ++j) {
            c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
        }
    }
    memset(f, 0xff, sizeof f);
    while (~scanf("%d%d", &n, &m)) printf("%d\n", dfs(n, m));
    return 0;
}

[51nod 1667] 概率好题

题意

甲乙进行比赛。

他们各有 $ k_1 $ , $ k_2 $ 个集合 $ [L_i,R_i] \cap Z $

每次随机从他们拥有的每个集合中都取出一个数

$ S_1=\sum $ 甲取出的数, $ S_2 $ 同理

若 $ S_1>S_2 $ 甲胜 若 $ S_1=S_2 $ 平局 否则乙胜

分别求出甲胜、平局、乙胜的概率。

数据范围: $ 1≤k_1, k_2≤8,1≤L≤R≤1e7 $

题解

设甲选出的数是 $ R_i - x_i, ; 0≤x_i≤R_i - L_i $ ,乙选出的数是 $ L_i + y_i, ; 0≤y_i≤R_i - L_i $

那么甲胜出的条件就是 $ \sum R_i - x_i > \sum L_i + y_i $ ,即:

$ \sum x_i + y_i + k = \sum R_i - \sum L_i - 1 $

$ k $ 是引入的一个变量,使我们可以将不等号化为等号。显然 $ k ≥ 0 $

我们要计算这个方程的解数。注意到未知数不超过 $ 17 $ 个,我们直接枚举那些不符合上界的数容斥即可。

#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define LL long long
#define pii pair<int, int>
using namespace std;
const int N = 1 << 20;
const int mod = 1e9 + 7;

template <typename T> void read(T &x) {
    int f = 0;
    register char c = getchar();
    while (c < '0' || c > '9') f |= (c == '-'), c = getchar();
    for (x = 0; c >= '0' && c <= '9'; c = getchar())
        x = (x << 3) + (x << 1) + (c ^ '0');
    if (f) x = -x;
}

inline int add(register int x, register int y) {
    static int z;
    return ((z = x + y) >= mod) ? (z - mod) : z;
}

inline int sub(register int x, register int y) {
    return (x < y) ? (x - y + mod) : (x - y);
}

inline void upd(int &x, register int y) {
    static int z;
    x = ((z = x + y) >= mod) ? (z - mod) : z;
}

int Qpow(int x, int p) {
    int ans = 1;
    while (p) {
        if (p & 1) ans = 1LL * ans * x % mod;
        x = 1LL * x * x % mod;
        p >>= 1;
    }
    return ans;
}

int Inv(int x) {
    return Qpow(x, mod - 2);
}

LL fac[N], fav[N], inv[N], pw[N];

inline LL C(int x, int y) {
    if (x < y || y < 0) return 0;
    int ans = fav[y];
    for (int i = 0; i < y; ++i) {
        ans = 1LL * ans * (x - i) % mod;
    }
    return ans;
}

void init() {
    fac[0] = fav[0] = 1;
    inv[1] = fac[1] = fav[1] = 1;
    for (int i = 2; i < N; ++i) {
        inv[i] = -mod / i * inv[mod % i] % mod + mod;
        fac[i] = fac[i - 1] * i % mod;
        fav[i] = fav[i - 1] * inv[i] % mod;
    }
    pw[0] = 1;
    for (int i = 1; i < N; ++i) {
        pw[i] = pw[i - 1] * 2 % mod;
    }
}

int n, m, tot, k1, k2;
int lim[N], a[N];
int l1[N], r1[N], l2[N], r2[N], bitcnt[N];

int calc() {
    int ans = 0;
    for (int i = 0; i < (1 << m - 1); ++i) {
        bitcnt[i] = bitcnt[i >> 1] + (i & 1);
        int sum = 0;
        for (int j = 1; j < m; ++j) {
            if (i >> (j - 1) & 1) {
                sum += lim[j] + 1;
            }
        }
        int del = C(n - sum + m - 1, m - 1);
        if (bitcnt[i] & 1) upd(ans, mod - del);
        else upd(ans, del);
    }
    return ans;
}

void solve() {
    m = n = 0;
    tot = 1;
    read(k1);
    for (int i = 1; i <= k1; ++i) {
        read(l1[i]);
        read(r1[i]);
        lim[++m] = r1[i] - l1[i];
        tot = 1LL * tot * (r1[i] - l1[i] + 1) % mod;
        n += r1[i];
    }
    read(k2);
    for (int i = 1; i <= k2; ++i) {
        read(l2[i]);
        read(r2[i]);
        lim[++m] = r2[i] - l2[i];
        tot = 1LL * tot * (r2[i] - l2[i] + 1) % mod;
        n -= l2[i];
    }
    ++m; --n;
    LL s1 = calc();
    n = 0;
    for (int i = 1; i <= k1; ++i) {
        n -= l1[i];
    }
    for (int i = 1; i <= k2; ++i) {
        n += r2[i];
    }
    --n;
    LL s2 = calc();
    LL s3 = sub(tot, add(s1, s2));
    LL inv = Inv(tot);
    printf("%lld %lld %lld\n", s1 * inv % mod, s3 * inv % mod, s2 * inv % mod);
}

int main() {
    init();
    int T;
    read(T);
    while (T--) solve();
    return 0;
}

[codeforces 724F] Uniformly Branched Trees

题意

求两两不同构的 $ n $ 个点无标号无根树的个数,满足所有非叶子节点的度为 $ d $

数据范围: $ n≤1000,d≤10 $

题解

一棵树最多有不超过两个重心。如果我们强制以树的重心为根,可以将问题转化为有根树的同构问题。

先处理一个 $ f_{i,j,k} $ 表示一棵大小为 $ i $ 的有根树,树根的度为 $ j $ ,它的子树(不计其本身)最大的大小不超过 $ k $ ,且内部节点满足度数限制的方案数。

转移有两种。其一是不选择大小为 $ k $ 的子树,贡献是 $ f_{i,j,k - 1} $ ,其二是选择 $ l≥1 $ 个大小为 $ k $ 的子树。可以选择的子树有 $ f_{k,d-1,k-1} $ 种,总共要选 $ l $ 棵,贡献就是 $ f_{i-kl,j-l,k-1}*\binom{f_{k,d-1,k-1} + l - 1}{f_{k,d-1,k-1}-1} $ ,注意到后面那个组合数等于 $ \binom{f_{k,d-1,k-1 + l - 1}}{l} $ ,于是可以在 $ O(d) $ 的时间计算这个组合数。需要特判的是,当 $ k=1 $ 的时候被加入的那个点度数可以是 $ 0 $ (直接接上一个叶子)。

如果 $ n $ 是奇数,那重心只能有唯一一个,答案就是 $ f_{n,d,\lceil \frac{n}{2}\rceil -1} $

如果 $ n $ 是偶数,重心还可能有两个。显然这两个应该是相邻的。删去这两个重心间连着的边,就得到两棵大小为 $ \frac{n}{2} $ 的树,贡献就是 $ \dbinom{f_{\frac{n}{2},d-1,\frac{n}{2}-1}+1}{2} $

还有就是特判 $ n=1 $ ,这是唯一允许度数为 $ 0 $ 的点出现的情况。

#include<bits/stdc++.h>
using namespace std;
const int N = 1005;

int n, d, mod;
int inv[N], fav[N], fac[N];
int f[N][11][N];

inline void upd(int &x, register int y) {
    static int z;
    x = ((z = x + y) >= mod) ? (z - mod) : z;
}

int C(int x, int y) {
    if (x < y || y < 0) return 0;
    int ans = 1;
    for (int i = 0; i < y; ++i) {
        ans = 1LL * ans * (x - i) % mod;
    }
    return 1LL * ans * fav[y] % mod;
}

int main() {
    cin >> n >> d >> mod;
    if (n == 1) return 0 * puts("1");
    fac[0] = fav[0] = 1;
    inv[1] = fac[1] = fav[1] = 1;
    for (int i = 2; i < N; ++i) {
        inv[i] = 1LL * -mod / i * inv[mod % i] % mod + mod;
        fac[i] = 1LL * fac[i - 1] * i % mod;
        fav[i] = 1LL * fav[i - 1] * inv[i] % mod;
    }
    for (int i = 0; i <= n; ++i) {
        f[1][0][i] = 1;
        f[1][d - 1][i] = 1;
    }
    for (int i = 2; i <= n; ++i) {
        for (int j = 1; j <= d; ++j) {
            for (int k = 1; k < i; ++k) {
                upd(f[i][j][k], f[i][j][k - 1]);
                for (int l = 1; l <= j && k * l  + 1 <= i; ++l) {
                    int del = f[i - k * l][j - l][min(k - 1, i - k * l - 1)];
                    del = 1LL * del * C(f[k][d - 1][k - 1] + l - 1, l) % mod;
                    upd(f[i][j][k], del);
                }
            }
        }
    }
    int ans = f[n][d][(n + 1) / 2 - 1];
    if (n % 2 == 0) {
        upd(ans, C(f[n / 2][d - 1][n / 2 - 1] + 1, 2));
    }
    cout << ans << endl;
    return 0;
}

[codeforces 960G] Bandit Blues

题意

有 $ n $ 个背包排成一排,他们价值的集合恰好是 $ {1,2,...n} $ 。一个人用这样的方法取背包:一开始手上拿着一个价值为 $ 0 $ 的背包,从左向右或者从右向左走,如果走到位置背包的价值大于他手中背包的价值,他就用它换掉手中的那个背包。已知这个人从左到右走会换 $ A $ 次背包,从右到左走会换 $ B $ 次背包,问可能的排列数。

数据范围: $ a,b,n≤200000 $ ,答案模 $ 998244353 $ 输出。

题解

一个显然的 $ DP $ 是:设 $ f_{i,j,k} $ 表示考虑价值前 $ i $ 大的背包,从左边走要换 $ j $ 次,从右边走要换 $ k $ 次的排列数。

转移就是考虑第 $ i $ 大的那个被加入到哪里。有三种情况。放在最左边:使 $ j $ 加一;放在最右边:使 $ k $ 加一;放在中间的 $ i-2 $ 个位置, $ j $ 和 $ k $ 都不变。

这样做是 $ O(n^3) $ 的,考虑优化这个 $ DP $ 。

第一个优化:注意到放在最左边和放在最右边乘上的系数是相同的,不如直接把它们看做一种转移,最后再将答案乘上一个 $ \dbinom{a+b}{a} $ 。即用 $ f_{i,j} $ 表示考虑了前 $ i $ 大的数,转移一和转移二共进行了 $ j $ 次的排列数。这样就得到了一个 $ O(n^2) $ 的 $ DP $ 。

第二个优化:列出现在的转移方程: $ f_{i,j} = (i-2) * f_{i-1,j} + f_{i - 1, j - 1} $ ,如果将 $ f_i $ 看做关于 $ j $ 的多项式,那么这次转移相当于乘上了一个 $ (i-2)+j $ ,于是我们只需要分治 $ FFT $ 求出 $ (0+x)(1+x)...(n-2+x) $ 和初始状态合并一下就得到了答案。这可以做到 $ O(nlogn) $ ,但是我的代码中实现的是 $ O(nlog^2n) $

#include<bits/stdc++.h>
#define LL long long
#define pb push_back
using namespace std;
const int G = 3;
const int N = 1 << 20;
const int mod = 998244353;

int n, a, b;
int rev[N];
int fac[N], fav[N], inv[N];

inline void upd(int &x, register int y) {
    static int z;
    x = ((z = x + y) >= mod) ? (z - mod) : z;
}

inline int add(register int x, register int y) {
    static int z;
    return ((z = x + y) >= mod) ? (z - mod) : z;
}

int C(int x, int y) {
    return 1LL * fac[x] * fav[y] % mod * fav[x - y] % mod;
}

int Qpow(int x, int p) {
    int ans = 1;
    while (p) {
        if (p & 1) ans = 1LL * x * ans % mod;
        x = 1LL * x * x % mod;
        p >>= 1;
    }
    return ans;
}

int Inv(int x) {
    return Qpow(x, mod - 2);
}

void calrev(int lim, int l) {
    for (int i = 0; i < lim; ++i) {
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << l - 1);
    }
}

void NTT(int *a, int lim, int type) {
    for (int i = 0; i < lim; ++i) {
        if (i < rev[i]) swap(a[i], a[rev[i]]);
    }
    for (int len = 2; len <= lim; len <<= 1) {
        int Wn = Qpow(G, (mod - 1) / len), mid = len >> 1;
        for (int i = 0; i < lim; i += len) {
            int W = 1;
            for (int j = 0; j < mid; ++j) {
                int x = a[i + j], y = 1LL * a[i + j + mid] * W % mod;
                a[i + j] = add(x, y);
                a[i + j + mid] = add(x, mod - y);
                W = 1LL * W * Wn % mod;
            }
        }
    }
    if (type == -1) {
        reverse(a + 1, a + lim);
        int tmp = Inv(lim);
        for (int i = 0; i < lim; ++i) {
            a[i] = 1LL * a[i] * tmp % mod;
        }
    }
}

vector<int> mul(vector<int> a, vector<int> b) {
    int n = a.size() - 1, m = b.size() - 1;
    static int A[N], B[N];
    for (int i = 0; i <= n; ++i) A[i] = a[i];
    for (int i = 0; i <= m; ++i) B[i] = b[i];
    int lim = 1, l = 0;
    while (lim <= n + m + 2) {
        lim <<= 1;
        ++l;
    }
    for (int i = n + 1; i < lim; ++i) A[i] = 0;
    for (int i = m + 1; i < lim; ++i) B[i] = 0;
    calrev(lim, l);
    NTT(A, lim, 1);
    NTT(B, lim, 1);
    for (int i = 0; i < lim; ++i) {
        A[i] = 1LL * A[i] * B[i] % mod;
    }
    NTT(A, lim, -1);
    vector<int> ans;
    for (int i = 0; i <= n + m; ++i) {
        ans.pb(A[i]);
    }
    return ans;
}

vector<int> solve(int l, int r) {
    vector<int> ans;
    if (l == r) {
        ans.pb(l);
        ans.pb(1);
        return ans;
    }
    int mid = (l + r) >> 1;
    ans = mul(solve(l, mid), solve(mid + 1, r));
    return ans;
}

int main() {
    int n, a, b;
    cin >> n >> a >> b;
    if (n == 1) {
        if (a == 1 && b == 1) puts("1");
        else puts("0");
        return 0;
    }
    if (a == 0 || b == 0) return 0 * puts("0");
    vector<int> res = solve(0, n - 2);
    vector<int> tmp;
    tmp.pb(0); tmp.pb(0); tmp.pb(1);
    res = mul(res, tmp);
    fac[0] = fav[0] = 1;
    inv[1] = fac[1] = fav[1] = 1;
    for (int i = 2; i < N; ++i) {
        inv[i] = 1LL * -mod / i * inv[mod % i] % mod + mod;
        fac[i] = 1LL * fac[i - 1] * i % mod;
        fav[i] = 1LL * fav[i - 1] * inv[i] % mod;
    }
    if (res.size() < a + b + 1) return 0 * puts("0");
    cout << 1LL * C(a + b - 2, a - 1) * res[a + b] % mod << endl;
    return 0;
}

[JLOI2016] 成绩比较

题意

G系共有 $ n $ 位同学, $ M $ 门必修课。这 $ N $ 位同学的编号为 $ 0 $ 到 $ N-1 $ 的整数,其中B神的编号为 $ 0 $ 号。这 $ M $ 门必修课编号为 $ 0 $ 到 $ M-1 $ 的整数。一位同学在必修课上可以获得的分数是 $ 1 $ 到 $ U_i $ 中的一个整数。如果在每门课上A获得的成绩均小于等于B获得的成绩,则称A被B碾压。在B神的说法中,G系共有 $ K $ 位同学被他碾压(不包括他自己),而其他 $ N-K-1 $ 位同学则没有被他碾压。D神查到了B神每门必修课的排名。这里的排名是指:如果B神某门课的排名为 $ R $ ,则表示有且仅有 $ R-1 $ 位同学这门课的分数大于B神的分数,有且仅有 $ N-R $ 位同学这门课的分数小于等于B神(不包括他自己)。我们需要求出全系所有同学每门必修课得分的情况数,使其既能满足B神的说法,也能符合D神查到的排名。这里两种情况不同当且仅当有任意一位同学在任意一门课上获得的分数不同。你不需要像D神那么厉害,你只需要计算出情况数模 $ 10^9+7 $ 的余数就可以了。

数据范围: $ n,m≤100,u_i≤10^9,1≤r_i≤n $

题解

设 $ f_{i,j} $ 为考虑前 $ i $ 门课,有 $ j $ 个人被碾压的方案数,于是有:

$ f_{i,j} = \sum_{k=j}^{n - 1} f_{i-1,k} \dbinom{k}{j} \dbinom{n-k - 1}{r_i - 1 - k + j} \sum_{l=1}^{u_i} l^{n-r_i}(u_i - l) ^ {r_i - 1} $

不妨设 $ g(x) = \sum_{i = 1} ^ {x} i^{n-r} (x - i) ^ {r_i - 1} $ 。手动二项式展开一下,会发现这是关于 $ x $ 的 $ n $ 多项式(注意 $ \sum $ 会使次数升高一次),直接上拉格朗日插值就OK

#include<bits/stdc++.h>
#define LL long long
#define pb push_back
using namespace std;
const int G = 3;
const int N = 205;
const int mod = 1e9 + 7;

int n, m, k;
int fac[N], fav[N], inv[N];
int u[N], r[N], f[N][N];

inline void upd(int &x, register int y) {
    static int z;
    x = ((z = x + y) >= mod) ? (z - mod) : z;
}

inline int add(register int x, register int y) {
    static int z;
    return ((z = x + y) >= mod) ? (z - mod) : z;
}

int C(int x, int y) {
    if (x < y || y < 0) return 0;
    return 1LL * fac[x] * fav[y] % mod * fav[x - y] % mod;
}

int Qpow(int x, int p) {
    int ans = 1;
    while (p) {
        if (p & 1) ans = 1LL * x * ans % mod;
        x = 1LL * x * x % mod;
        p >>= 1;
    }
    return ans;
}

int Inv(int x) {
    return Qpow(x, mod - 2);
}

void prework() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= m; ++i) {
        scanf("%d", &u[i]);
    }
    for (int i = 1; i <= m; ++i) {
        scanf("%d", &r[i]);
    }
    fac[0] = fav[0] = 1;
    inv[1] = fac[1] = fav[1] = 1;
    for (int i = 2; i < N; ++i) {
        inv[i] = 1LL * -mod / i * inv[mod % i] % mod + mod;
        fac[i] = 1LL * fac[i - 1] * i % mod;
        fav[i] = 1LL * fav[i - 1] * inv[i] % mod;
    }
}

int calc(int x, int r) {
    static int y[N];
    for (int i = 0; i <= n; ++i) {
        y[i] = 0;
        for (int j = 0; j <= i; ++j) {
            upd(y[i], 1LL * Qpow(j, n - r) * Qpow(i - j, r - 1) % mod);
        }
    }
    int ans = 0;
    for (int i = 0; i <= n; ++i) {
        int tmp = 1;
        for (int j = 0; j <= n; ++j) {
            if (i == j) continue;
            tmp = 1LL * tmp * (x - j) % mod;
            tmp = 1LL * tmp * Inv(i - j) % mod;
        }
        if (tmp < 0) tmp += mod;
        upd(ans, 1LL * tmp * y[i] % mod);
    }
    return ans;
}

void solve() {
    f[0][n - 1] = 1;
    for (int i = 1; i <= m; ++i) {
        int t = calc(u[i], r[i]);
        for (int j = 0; j <= n; ++j) {
            for (int k = j; k <= n; ++k) {
                int del = f[i - 1][k];
                del = 1LL * del * C(k, j) % mod;
                del = 1LL * del * C(n - k - 1, r[i] - 1 - k + j) % mod;
                upd(f[i][j], del);
            }
            f[i][j] = 1LL * f[i][j] * t % mod;
        }
    }
    printf("%d\n", f[m][k]);
}

int main() {
    prework();
    solve();
    return 0;
}

原文地址:https://www.cnblogs.com/Vexoben/p/11728887.html

时间: 2024-11-05 16:26:22

组合计数小练的相关文章

HDU 4832 组合计数dp

Chess Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 509    Accepted Submission(s): 198 Problem Description 小度和小良最近又迷上了下棋.棋盘一共有N行M列,我们可以把左上角的格子定为(1,1),右下角的格子定为(N,M).在他们的规则中,"王"在棋盘上的走法遵循十字

Yue Fei&#39;s Battle(组合计数递推)

//求一个直径为 k 的树有多少种形态,每个点的度不超过 3 // 非常完美的分析,学到了,就是要细细推,并且写的时候要细心 还有除法取模需要用逆元 #include <iostream> #include <stdio.h> #include <string.h> #include <math.h> #include <stdlib.h> using namespace std; #define MOD 1000000007 #define L

3.12课程小练以及vim编辑器的基本用法(部分)

 课程小练 1. 通过man帮助手册,自行总结which.whereis.whatis命令的使用方法: which:展示出shell命令的完整路径: 格式: which [options] [--] programname [...] whereis:基于二进制的,有源的,和文件或命令的记录手册 格式:whereis [options] name... whatis:在 whatis 数据库里查找完整的单词 格式:whatis - 2. du命令: du - 报告磁盘空间使用情况 du -a,

POJ 1496 POJ 1850 组合计数

Code Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 8256 Accepted: 3906 Description Transmitting and memorizing information is a task that requires different coding systems for the best use of the available space. A well known system is t

bzoj 1004 Cards 组合计数

这道题考察的是组合计数(用Burnside,当然也可以认为是Polya的变形,毕竟Polya是Burnside推导出来的). 这一类问题的本质是计算置换群(A,P)中不动点个数!(所谓不动点,是一个二元组(a,p),a∈A,p∈P ,使得p(a)=a,即a在置换p的作用后还是a). Polya定理其实就是告诉了我们一类问题的不动点数的计算方法. 对于Burnside定理的考察,我见过的有以下几种形式(但归根结底还是计算不动点数): 1.限制a(a∈A)的特点,本题即是如此(限制了各颜色个数,可以

POJ 3252 组合计数

Round Numbers Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 9149 Accepted: 3248 Description The cows, as you know, have no fingers or thumbs and thus are unable to play Scissors, Paper, Stone' (also known as 'Rock, Paper, Scissors', 'Ro,

HDU4675-GCD of Sequence(数论+组合计数)

GCD of Sequence Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others) Total Submission(s): 949    Accepted Submission(s): 284 Problem Description Alice is playing a game with Bob. Alice shows N integers a1, a2, -, aN, an

[HDU 3461] Saving Beans &amp; 组合计数Lucas定理模板

Saving Beans Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Problem Description Although winter is far away, squirrels have to work day and night to save beans. They need plenty of food to get through those long cold

POJ 1019 组合计数

链接:POJ 1019 /***************************************** author : Grant Yuan time : 2014/10/19 14:38 source : POJ 1019 algorithm: 组合计数 ******************************************/ #include <iostream> #include <cstdio> #include <algorithm> #