野花做了场玫瑰梦。|

_Tomori

园龄:5个月粉丝:7关注:11

2025-01-01 20:30阅读: 30评论: 0推荐: 0

【组合数学】二项式相关与容斥

二项式定理

(a+b)n=i=0n(ni)aibni

证明:

数学方法。

(a+b)n=a×(a+b)n1=a×b×(a+b)n2=

假设我们选了 ka,我们就需要选 nkb,根据乘法原理,可以得到 akbnk,由于每次是 nka,故得到 (nk),最终加和求得。

证毕。

其中 (ni) 为二项式系数。

特殊情况

a=1,b=1,得到:

(1+(1))n=i=0n(1)n(ni)

n=0 时:

(00)(1)0=1

因此得出:

i=0n(1)n(ni)=[n=0]

其中方括号为艾弗森括号。

常用的二项式推论

(nm)=(nm1)+(n1m1) (nm)=nm(n1m1) (ni)(ij)=(nj)(njij)(i,jn) i=0n(ni)=2n i=0m(ni)(mki)=(n+mk)

容斥

容斥,说人话,即为在统计方案数出现重复计数的情况时,我们需要使用容斥求解。

简单容斥

已知集合 |S1|,|S2|,求 |S1S2|

最简单的容斥,只需要减去它们之间重叠的部分即可。

|S1S2|=|S1|+|S2||S1S2|

若已知集合 |S1|,|S2|,|S3|,求 |S1S2S3|

可以用韦恩图求解。

|S1S2S3|=|S1|+|S2|+|S3||S1S2||S1S3||S2S3|+|S1S2S3|

对于更多集合的容斥:

|i=1nSi|=i=1n|Si|1i<jn|SiSj|+1i<j<kn|SiSjSk|+(1)n1i=1ni=1n|Si|=i=1n(1)i1ai<ai+1|i=1nSai|

二项式反演

在已知函数 fngn 存在特定关系,即 fnn 项物品构成特定结构的方案数,gn 为从 n 中选出 i0 项物品构成特殊结构的方案数,在已知 fn 的情况下我们很容易可以得到:

gn=i=0n(ni)fn

那如果我们只知道 gn 呢?

考虑递推,从 1 项物品推到 n 项物品,显然的我们会发现有情况会算重,因此需要带容斥系数。

fn=i=1n(1)ni(ni)gn

对于这种特殊关系:当题目中出现恰好需要 n 项物品构成特殊结构的方案数,可以转换成 fn 表示恰好需要 n 项物品构成特殊结构的方案数,gn 表示至少 / 至多需要 n 项物品构成特殊结构的方案数,题目中出现至少 / 至多需要亦可。

例题

P10596 BZOJ2839 集合计数

fi 表示集合交集元素恰为 i 的方案数,gi 表示选出 j 个子集交集元素为 i 的方案数,发现它们满足二项式反演的关系,即 fii 个不同元素构成特定结构的方案数时 gi 是从 i 个中选出 j0 个构成特定结构的方案数,fk 即为我们想要的答案,得到以下柿子:

gn=i=0n(ni)fifn=i=0n(1)ni(ni)gi

所以推出 fk 的柿子,但是在 nk 中取会算重,所以应该是取至少 k,得到:

fk=i=kn(1)ik(ik)gi

在剩下 nk 个中,选的方案数 (ni),任意选或不选,方案数是 2nk,再从这些集合中选出若干个非空集合方案数是 22nk1,得到 gk

gk=i=kn(ni)(22ni1)

答案就是 gk

参考代码:

#include <bits/stdc++.h>
#define int long long

using namespace std;
const int maxn = 1e6 + 10, mod = 1e9 + 7;
int n, k, fac[maxn], invfac[maxn], g[maxn];

void write (__int128_t x) {
    if (x > 9) write (x / 10);
    if (x < 0) x = -x, putchar ('-');
    putchar (x % 10 + '0');
}

inline int binpow (int x, int k, int mod) {
    int ret = 1;
    while (k) {
        if (k & 1) ret = ret * x % mod;
        x = x * x % mod;
        k >>= 1;
    }
    return ret;
}

inline void getFac() {
    fac[0] = 1;
    for (int i = 1; i <= 1e6; i ++)
        fac[i] = fac[i - 1] * i % mod;
    invfac[1000000] = binpow (fac[1000000], mod - 2, mod);
    for (int i = 999999; i >= 0; i --)
        invfac[i] = invfac[i + 1] * (i + 1) % mod;
    return;
}

inline int Com (int n, int m) {
    if (n < 0 || m < 0  || m > n) return 0;
    return fac[n] * invfac[m] % mod * invfac[n - m] % mod;
}

inline void Solve() {
    __int128_t Answer = 0;
    for (int i = k; i <= n; i ++) {
        int tmp = binpow (2, n - i, mod - 1);
        g[i] = Com (n, i) % mod * (binpow (2, tmp, mod) - 1) % mod;
    }
    for (int i = k; i <= n; i ++) 
        Answer = (Answer + (((i - k) & 1) ? -1 : 1) * Com (i, k) % mod * g[i] % mod + mod) % mod;
    write (Answer), putchar ('\n');
    return;
}

signed main() {
    scanf ("%lld %lld", &n, &k);
    getFac(), Solve();
    return 0;
}

CF1400G Mercenaries

区间的处理可以开桶差分,注意到 m20,考虑状压,对于一个状态 st,它在二进制下当前位为 1 表示需要选当前位,0 反之,然后就是容斥:至少 01 至少 11 + 至少 21 ,以上均讨论方案数。

接着是动规,记 dpi,j 表示前 i 个选 j 人出征的方案,转移方程:

dpi,j=j=02mdpi1,j+(tijij)

t 为给区间开的桶。

接着找到对于冲突的 ai,bi 的最大 l 和最小 r,答案:

st=02m1(1)popcount(st)×(dpmnr,tmpCntdpmxl1,tmpCnt)

其中 tmpCnt 为开桶统计的出现的不同的人的个数。

参考代码:

#include <bits/stdc++.h>
#define int long long

using namespace std;
const int maxn = 3e5 + 10, mod = 998244353;
int n, m, buck[maxn], dp[maxn][50], fac[maxn], invfac[maxn], tmpArr[maxn], mxp, mnp, tmpCnt, Answer;
struct Ayaka { int x, y; } sol[maxn], sat[25];

inline int binpow (int x, int k) {
    int ret = 1;
    while (k) {
        if (k & 1) ret = ret * x % mod;
        x = x * x % mod;
        k >>= 1;
    }
    return ret;
}

inline void Getfac() {
    fac[0] = invfac[0] = 1;
    for (int i = 1; i <= 3e5; i ++)
        fac[i] = fac[i - 1] * i % mod;
    invfac[300000] = binpow (fac[300000], mod - 2);
    for (int i = 299999; i >= 1; i --)
        invfac[i] = invfac[i + 1] * (i + 1) % mod;
    return;
}

inline int Com (int n, int m) {
    if (n < 0 || m < 0 || m > n) return 0;
    return fac[n] * invfac[m] % mod * invfac[n - m] % mod;
}

inline int popcount (int st) {
    int tmpCnt = 0;
    while (st) {
        tmpCnt += (st % 2 ? 1 : 0);
        st /= 2;
    }
    return tmpCnt;
}

signed main() {
    scanf ("%lld %lld", &n, &m), Getfac();
    for (int i = 1; i <= n; i ++) {
        scanf ("%lld %lld", &sol[i].x, &sol[i].y);
        buck[sol[i].x] ++, buck[sol[i].y + 1] --;
    }
    for (int i = 1; i <= n; i ++) 
        buck[i] += buck[i - 1];
    for (int i = 1; i <= m; i ++) 
        scanf ("%lld %lld", &sat[i].x, &sat[i].y);
    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j <= (m << 1); j ++)
            dp[i][j] = (dp[i - 1][j] + Com (buck[i] - j, i - j)) % mod;
    }
    for (int st = 0; st < (1 << m); st ++) {
        mxp = 1, mnp = n, tmpCnt = 0;
        for (int i = 1; i <= m; i ++) {
            if ((st >> (i - 1)) & 1) {
                mxp = max (mxp, max (sol[sat[i].x].x, sol[sat[i].y].x));
                mnp = min (mnp, min (sol[sat[i].x].y, sol[sat[i].y].y));
                if (!tmpArr[sat[i].x]) tmpCnt ++;
                tmpArr[sat[i].x] ++;
                if (!tmpArr[sat[i].y]) tmpCnt ++;
                tmpArr[sat[i].y] ++;
            }
        }
        for (int i = 1; i <= m; i ++) {
            if ((st >> (i - 1)) & 1) 
                tmpArr[sat[i].x] = tmpArr[sat[i].y] = 0;
        }
        if (mxp > mnp) 
            continue;
        int tot = popcount (st);
        Answer = (Answer + ((tot & 1) ? -1 : 1) * (dp[mnp][tmpCnt] - dp[mxp - 1][tmpCnt]) % mod + mod) % mod;
    }
    printf ("%lld\n", Answer);
    return 0;
}

P8737 [蓝桥杯 2020 国 B] 质数行者

正常 dp 思路实现是 O(n3cnt),其中 cnt 为质数个数,结合容斥我们可以从前两维向第三维合并,具体一点:

dpi,j,k=i=0nj=0mk=0wdpiprimet,j,k×dpi,jprimet,k×dpi,j,kprimet×(i+j+k)!i!j!k!

dpi,j=i=0nj=0mdpiprimet,j1

gi+j=i=0nj=0mdpn,i×dpm,j×(i+j)!i!j!

tmpSum=i=0n+mj=0wgi×dpw,j×(i+j)!i!j!

答案需要容斥计算: (1,1,1)(n,m,w) 的方案减去 (1,1,1)(ri,hi,ci)(n,m,w) 的方案数,其中 i{1,2},加上 (1,1,1)(r1,h1,c1)(r2,h2,c2)(n,m,w) 的方案数和 (1,1,1)(r2,h2,c2)(r1,h1,c1)(n,m,w) 的方案数。

参考代码:

#include <bits/stdc++.h>
#define int long long

using namespace std;
inline void read (int &x) {
    int res (0), f (1);
    char ch (getchar());
    while (!isdigit (ch)) f = ch == '-' ? -1 : 1, ch = getchar();
    while (isdigit (ch)) res = (res << 1) + (res << 3) + (ch ^ 48), ch = getchar();
    x = res * f;
}

const int maxn (1e3 + 5), mod (1e9 + 7);
int n, m, w, r[3], c[3], h[3], dp[maxn][maxn], fac[maxn << 2], invfac[maxn << 2], prime[maxn << 2], idx, maxCube, g[maxn << 2];
bool isprime[maxn << 2];

inline int binpow (int x, int k)  {
    int tmpVal (1);
    while (k) {
        if (k & 1) tmpVal = tmpVal * x % mod;
        x = x * x % mod;
        k >>= 1;
    }
    return tmpVal;
}

inline void Getfac() {
    fac[0] = invfac[0] = 1;
    for (int i (1); i <= 3e3; i ++)
        fac[i] = fac[i - 1] * i % mod;
    invfac[3000] = binpow (fac[3000], mod - 2);
    for (int i (2999); i >= 1; i --)
        invfac[i] = invfac[i + 1] * (i + 1) % mod;
    return;
}

inline void Getprime() {
    for (int i (2); i <= 3e3; i ++) {
        if (!isprime[i]) prime[++ idx] = i;
        for (int j (1); j <= idx && i * prime[j] <= 3e3; j ++) {
            isprime[i * prime[j]] = 1;
            if (!(i % prime[j])) break;
        }
    }
    return;
}

inline void GetDp () {
    dp[1][0] = 1;
    for (int i (2); i <= maxCube; i ++) {
        for (int k (1); k <= i; k ++) {
            for (int j (1); j <= idx; j ++) {
                if (prime[j] > i) continue;
                dp[i][k] = (dp[i][k] + dp[i - prime[j]][k - 1]) % mod;
            }
        }
    }
    return;
}

inline int MergeDp (int x, int y, int z, int fx, int fy, int fz) {
    int dx (fx - x + 1), dy (fy - y + 1), dz (fz - z + 1);
    if (dx < 0 || dy < 0 || dz < 0) return 0;
    fill (g, g + dx + dy + 1, 0);
    for (int i (0); i <= dx; i ++) {
        for (int j (0); j <= dy; j ++)
            g[i + j] = (g[i + j] + fac[i + j] * invfac[i] % mod * invfac[j] % mod * dp[dx][i] % mod * dp[dy][j] % mod) % mod;
    }
    int tmpSum (0);
    for (int i (0); i <= dx + dy; i ++) {
        for (int j (0); j <= dz; j ++)
            tmpSum = (tmpSum + fac[i + j] * invfac[i] % mod * invfac[j] % mod * g[i] % mod * dp[dz][j] % mod) % mod;
    }
    return tmpSum;
}

signed main() {
    Getfac(), Getprime();
    read (n), read (m), read (w);
    for (int i : {1, 2}) read (r[i]), read (c[i]), read (h[i]);
    maxCube = max (n, max (m, w)), GetDp();
    int Answer (MergeDp (1, 1, 1, n, m, w));
    for (int i : {1, 2}) {
        int tmpAns = MergeDp (1, 1, 1, r[i], c[i], h[i]) * MergeDp (r[i], c[i], h[i], n, m, w) % mod;
        Answer = ((Answer - tmpAns) % mod + mod) % mod;
    }
    int tmpAns_A (MergeDp (1, 1, 1, r[1], c[1], h[1]) * MergeDp (r[1], c[1], h[1], r[2], c[2], h[2]) % mod * MergeDp (r[2], c[2], h[2], n, m, w) % mod);
    int tmpAns_B (MergeDp (1, 1, 1, r[2], c[2], h[2]) * MergeDp (r[2], c[2], h[2], r[1], c[1], h[1]) % mod * MergeDp (r[1], c[1], h[1], n, m, w) % mod);
    Answer = (((Answer + tmpAns_A) % mod + tmpAns_B) % mod) % mod;
    printf ("%lld\n", Answer);
    return 0;
}

习题

P6521 [CEOI2010 day2] pin

P5505 [JSOI2011] 分特产

P4859 已经没有什么好害怕的了

P10986 [蓝桥杯 2023 国 Python A] 2023

本文作者:Tomori's Blog

本文链接:https://www.cnblogs.com/Tomori0505/p/18646263

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   _Tomori  阅读(30)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起