组合数学总结

历时九天,组合数学快要结束了,写篇博客总结一下

emmm......众所周知,数学一直是我的一个薄弱i项,这次也算是一个很好的锻炼

先列一下这次都用了哪些知识点

1.组合数(废话)

2.逆元

3.卢卡斯定理

4.数据结构(数状数组/线段树)

5.高精

6.暴力打表,瞎猜规律

先来几道水题

A.排队

这题一度让我体会到高精的恐惧

式子很简单:(n+2)!*A(n+3,m)-(n+1)!*2*A(n+2,m)

B.perm排列计数

只要把题中的pi>pi/2转化成pi<pi<<1,pi<pi<<1|1就好了,二叉树上简单的树形dp,需要用到卢卡斯

式子:dp[i]=dp[i<<1]*dp[i<<1|1]*C(size[i]-1,size[i<<1])

C.地精部落

这题网上有许多解法,这里只介绍组合数解法

其实和perm的dp差不多,我们可以发现对于k个数来说每个数是多少没有影响,而每加一个数只能成为山峰

式子:dp[i][(i-j-1)&1]=dp[i][(i-j-1)&1]+1ll*dp[j][1]*dp[i-j-1][(i-j-1)&1]*C[i-1][j];

组合数直接杨辉三角就好了

接下来高能预警

D.集合计数

一道卡了好久的题

直接计算不好解,我们考虑容斥

以C(n,k)*2^2^(n-k)为全集

减去C(n,k+1)*2^2^(i-k-1)×C(k+1,k)

加上C(n,k+2)*2^2^(i-k-2)×C(k+2,k)

......

则ans=∑C(n,i)*2^2^(i-k)×C(i,k)*(-1)i-k (k<=i<=n)

 1 #include <iostream>
 2 #include <cstdio>
 3 #define re register
 4 #define ll long long
 5 const int mod = 1000000007;
 6 using namespace std;
 7 inline int pow(int a, int b) {
 8     re int ans = 1;
 9     for (; b; b >>= 1, a = 1ll * a * a % (mod))
10         if (b & 1)
11             ans = 1ll * ans * a % (mod);
12     return ans;
13 }
14 int j[1000002], ni[1000002], ids[1000002];
15 inline int C(int n, int m) { return 1ll * j[n] * ni[m] % mod * ni[n - m] % mod; }
16 int main() {
17     re int n, k;
18     re ll ans = 0;
19     scanf("%d%d", &n, &k);
20     ids[0] = j[0] = 1;
21     for (re int i = 1; i <= n; i++)
22         j[i] = 1ll * j[i - 1] * i % mod, ids[i] = 1ll * ids[i - 1] * 2 % (mod - 1);
23     ni[n] = pow(j[n], mod - 2);
24     for (int i = n; i >= 1; i--) ni[i - 1] = 1ll * ni[i] * i % mod;
25     for (re int i = k, js = 1; i <= n; i++, js ^= 1) {
26         if (js)
27             ans = (ans + 1ll * C(n, i) * pow(2, ids[n - i]) % mod * C(i, k) % mod) % mod;
28         else
29             ans = (ans - 1ll * C(n, i) * pow(2, ids[n - i]) % mod * C(i, k) % mod + mod) % mod;
30     }
31     printf("%lld", ans);
32     return 0;
33 }
View Code

E.看电影(movie)

这题做的时候没想那么多,因为前面几道道排列用了dp,先想的dp

然后发现这题不像那几道题放几个数对结果没有影响,主要受后移影响较大

然后我们思考,怎么把后移操作干掉,然后就想到了加一个位子来构成一个环

把所有人放到环上之后,每个空格一定没有被经过,则可以任选一个空格断开

又因为在选的时候每种环都算了(k+1)次,总方案除以(k+1)

ans=(k+1)n-1*(k+1-n)/kn

高精算一下,用分解质因数或gcd约分

(当然也可以暴力打表,然后就看出来规律了)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int p[255], top, v[255], num[255], ens[255];
struct node {
    short m[12010];
    friend node operator*(node a, int b) {
        int now = 0;
        for (int i = 1; i <= a.m[0]; i++) {
            now = a.m[i] = a.m[i] * b + now;
            now /= 10;
            a.m[i] %= 10;
        }
        int i = a.m[0] + 1;
        while (now) {
            a.m[i] = now % 10;
            now /= 10;
            a.m[0] = i;
            i++;
        }
        return a;
    }
} one, fro, en;
void put(node a) {
    for (int i = a.m[0]; i >= 1; i--) putchar(a.m[i] + '0');
}
int main() {
    one.m[0] = one.m[1] = 1;
    for (int i = 2; i <= 200; i++) {
        if (!v[i])
            p[++top] = i;
        for (int j = 1; j <= top && p[j] * i <= 200; j++) {
            v[i * p[j]] = 1;
            if (!(i % p[j]))
                break;
        }
    }
    int t, n, k, nn, ns, kk;
    scanf("%d", &t);
    while (t--) {
        scanf("%d%d", &n, &k);
        if (n > k) {
            puts("0 1");
            continue;
        }
        memset(num, 0, sizeof num);
        memset(ens, 0, sizeof ens);
        fro = en = one;
        nn = k + 1;
        ns = k + 1 - n;
        kk = k + 1;
        for (int i = 1; i <= top && p[i] <= kk; i++) {
            while (!(nn % p[i])) num[i] += n, nn /= p[i], ens[i]++;
            while (!(ns % p[i])) num[i]++, ns /= p[i];
            while (!(k % p[i])) ens[i] += n, k /= p[i];
        }
        for (int i = 1; i <= top; i++) {
            while (num[i] && ens[i]) num[i]--, ens[i]--;
            for (int j = 1; j <= num[i]; j++) fro = fro * p[i];
            for (int j = 1; j <= ens[i]; j++) en = en * p[i];
        }
        put(fro);
        putchar(' ');
        put(en);
        puts("");
    }
}
View Code

F.虔诚的墓主人

一道混在数学里的数据结构题

首先我们发现k>=1,则每个空行空列都没有用,直接离散化

(然后我们可以一个个扫离散化后的矩阵然后就A了)

我们一列列扫每棵树,我们发现在同一列里,连续一段空格C(up,k)*C(down,k)都一样

我们考虑用数据结构维护C(left,k)*C(right,k)的和

每扫到一棵树,把它所在行的值更新

组合数不能用逆元,但是k很小,直接杨辉三角就好了

这个魔术模数也不需要取模,直接int自然溢出就好了

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
struct node {
    int x, y, xx, yy;
} p[100010];
int n, m, w, numx, numy, k, jie = 1, c[100010];
int as(node a, node b) { return a.x < b.x; }
int ad(node a, node b) {
    if (a.y == b.y)
        return a.x < b.x;
    return a.y < b.y;
}
void add(int x, int y) {
    for (int i = x; i <= w; i += i & -i) c[i] += y;
}
int get(int x) {
    int ans = 0;
    for (int i = x; i; i -= i & -i) ans += c[i];
    return ans;
}
int nx[100010], ny[100010], has[100010], now[100010], C[100010][11];
int main() {
    scanf("%d%d%d", &n, &m, &w);
    for (int i = 1; i <= w; i++) scanf("%d%d", &p[i].x, &p[i].y);
    scanf("%d", &k);
    C[0][0] = 1;
    for (int i = 1; i <= w; i++) {
        C[i][0] = 1;
        for (int j = 1; j <= min(i, k); j++) C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
    }
    sort(p + 1, p + w + 1, as);
    numx = 0;
    p[0].x = p[0].y = -1;
    for (int i = 1; i <= w; i++) {
        if (p[i].x != p[i - 1].x)
            p[i].xx = ++numx;
        else
            p[i].xx = numx;
        nx[p[i].xx]++;
    }
    sort(p + 1, p + w + 1, ad);
    numy = 0;
    for (int i = 1; i <= w; i++) {
        if (p[i].y != p[i - 1].y)
            p[i].yy = ++numy;
        else
            p[i].yy = numy;
        ny[p[i].yy]++;
    }
    int ans = 0, y;
    for (int i = 1; i <= w; i++) {
        has[p[i].xx]++;
        nx[p[i].xx]--;
        add(p[i].xx, -now[p[i].xx]);
        now[p[i].xx] = C[has[p[i].xx]][k] * C[nx[p[i].xx]][k];
        add(p[i].xx, now[p[i].xx]);
        if (p[i].yy != p[i - 1].yy) {
            y = 1;
            ny[p[i].yy]--;
            continue;
        }
        ans += (get(p[i].xx - 1) - get(p[i - 1].xx)) * C[y][k] * C[ny[p[i].yy]][k];
        y++;
        ny[p[i].yy]--;
    }
    if (ans < 0)
        ans += 2147483648;
    printf("%d", ans);
}
View Code

G.DZY Loves Math II

卡了三天除了暴力什么也不会打最后还看题解真的好吗

神奇的将背包和拆成了若干的S和y,

用k表示S的质因子数

则S*i这部分就变成了简单的组合数C(n/s+k-1,k-1)

剩下的部分最多7×S,直接暴力背包就好了,

又因为每个数所占不能超过S,我们把背包弄个前缀和减掉就好了

组合数很大,可以处理出来(k-1)!的逆元,然后O(k)求就好了

详细题解戳神犇Deepinc

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
const ll mod = 1000000007;
int p[14000002], v[14000002], top, num, has[10], sum, jie = 1;
ll C(ll a, ll b) {
    if (a < b)
        return 0;
    if (a == b || !b)
        return 1;
    ll ans = jie;
    for (ll i = a - b + 1; i <= a; i++) ans = i % mod * ans % mod;
    return ans;
}
int pow(int a, int b) {
    int ans = 1;
    for (; b; b >>= 1, a = 1ll * a * a % mod)
        if (b & 1)
            ans = 1ll * ans * a % mod;
    return ans;
}
ll min(ll a, ll b) { return a < b ? a : b; }
int main() {
    int s, q, ss;
    ll n, ans;
    scanf("%d%d", &s, &q);
    int a = s / 2;
    has[0] = s;
    for (int i = 2; i <= s; i++) {
        if (!v[i]) {
            p[++top] = i;
            if (!(s % i))
                s /= i, has[++num] = i, sum += i;
            if (!(s % i)) {
                while (q--) scanf("%lld", &n), puts("0");
                return 0;
            }
        }
        for (int j = 1; j <= top && p[j] * i <= a; j++) {
            v[i * p[j]] = 1;
            if (!(i % p[j]))
                break;
        }
    }
    if (!num)
        has[++num] = s, sum = s;
    ss = num * has[0];
    memset(v, 0, sizeof v);
    memset(p, 0, sizeof p);
    v[0] = 1;
    p[0] = 1;
    for (int i = 1; i <= num; i++)
        for (int j = 0; j < has[i]; j++) {
            for (int k = has[i] + j; k <= ss; k += has[i]) p[k] = (p[k - has[i]] + v[k]) % mod;
            for (int k = has[i] + j; k <= ss; k += has[i])
                v[k] = (p[k] - (k >= has[0] ? p[k - has[0]] : 0) + mod) % mod;
        }
    for (int i = 1; i < num; i++) jie = 1ll * jie * i % mod;
    jie = pow(jie, mod - 2);
    while (q--) {
        scanf("%lld", &n);
        n -= sum;
        if (n < 0) {
            puts("0");
            continue;
        }
        ans = 0;
        for (ll now = 0; now <= min(n, ss); now += has[0])
            ans = (ans + C((n - now) / has[0] + num - 1, num - 1) * v[now + n % has[0]] % mod) % mod;
        printf("%lld
", ans);
    }
    return 0;
}
View Code

这次许多题都很有难度,的确很好的锻炼了数学这方面的能力

 欢迎debug

${color{Teal} 只}$${color{Teal} 是}$${color{Teal} 拼}$${color{Teal} 凑}$${color{Teal} 出}$${color{Teal} 与}$${color{Teal} 你}$${color{Teal} 在}$${color{Teal} 一}$${color{Teal} 起}$${color{Teal} 的}$${color{Teal} 时}$${color{Teal} 间}$
原文地址:https://www.cnblogs.com/mikufun-hzoi-cpp/p/11131667.html