zoukankan      html  css  js  c++  java
  • 组合数学总结

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

    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} 间}$
  • 相关阅读:
    课堂作业02
    模仿JavaAppArguments.java示例,编写一个程序,此程序从命令行接收多个数字,求和之后输出结果。
    Feign使用Hystrix无效原因及解决方法
    解决Spring Boot 使用RedisTemplate 存储键值出现乱码 xacxedx00x05tx00
    consul怎么在windows下安装
    java运行jar命令提示没有主清单属性
    Maven parent.relativePath
    Maven的pom.xml文件结构之基本配置packaging和多模块聚合结构(微服务)
    redis开启远程访问
    kibana使用
  • 原文地址:https://www.cnblogs.com/mikufun-hzoi-cpp/p/11131667.html
Copyright © 2011-2022 走看看