水果拼盘(反演求容斥系数)

反演与容斥原理详解
本文通过一个具体的算法题目,介绍了如何运用反演和容斥原理解决组合数学中的复杂计数问题,并提供了完整的代码实现。

Description:

这里写图片描述

题解:

出题人本意是考反演,结果没学好期望线性可加。

最近一直在练容斥反演,所以一看就上容斥反演。

f(S)f(S)表示选的水果的集合是S的方案数,g(S)g(S′)表示选的水果是S’的子集的方案数。

显然g(S)g(S′)是非常好算的,记cntcnt表示拼盘里的水果都属于S’集的拼盘的个数,则g(S)=Ckcntg(S′)=Ccntk

ffg的关系是g(S)=SSf(S)g(S′)=∑S∈S′f(S)

根据反演套路,设容斥系数为hh

f(S)=SSg(S)h(S)

其中h(S)h(S′)为一个和S’有关的函数,展开gg得:

f(S)=SSh(S)SSf(S)
f(S)=S′′Sf(S′′)S′′SSh(S)f(S)=∑S″∈Sf(S″)∑S″∈S′∈Sh(S′)

使h(S)=(1)|S|h(S′)=(−1)|S′|,再把求出的f(S)f(S)也乘上(1)|S|(−1)|S|既可以得到ff,后面就简单了。

暴力枚举是会超时的,所以用宽搜+状压dp优化转移。

Code:

#include<bits/stdc++.h>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define fu(a) ((a) & 1 ? -1 : 1)
using namespace std;

const int mo = 998244353;

int n, m, k, x, y, a[20], b[20], a2[20], bz[100005];
ll c[100005][26], g[1 << 18], f[1 << 18][19], h[1 << 18], ans;

ll ksm(ll x, ll y) {
    ll s = 1;
    for(; y; y /= 2, x = x * x % mo)
        if(y & 1) s = s * x % mo;
    return s;
}

int main() {
    freopen("eat.in", "r", stdin);
    freopen("eat.out", "w", stdout);
    a2[0] = 1; fo(i, 1, 18) a2[i] = a2[i - 1] * 2;
    scanf("%d %d %d", &n, &m, &k);
    fo(i, 0, n) {
        c[i][0] = 1;
        fo(j, 1, min(i, k)) c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mo;
    }
    fo(i, 1, m) scanf("%d", &a[i]);
    fo(i, 1, m) scanf("%d", &b[i]);
    fo(i, 1, n) {
        scanf("%d", &y);
        fo(j, 1, y) {
            scanf("%d", &x);
            bz[i] |= a2[x - 1];
        }
        f[bz[i]][0] ++;
    }
    fo(i, 0, a2[m] - 1) {
        fo(j, 0, m - 1) if(f[i][j]) {
            fo(k, j, m - 1) if(!(i & a2[k]))
                f[i | a2[k]][k + 1] += f[i][j];
        }
        int s1 = 0; fo(j, 0, m - 1) s1 += (i & a2[j]) > 0;
        fo(j, 0, m) g[i] += f[i][j];
        g[i] = c[g[i]][k] * fu(s1);
    }
    memset(f, 0, sizeof f);
    fo(i, 0, a2[m] - 1) f[i][0] += g[i];
    fo(i, 0, a2[m] - 1) {
        fo(j, 0, m - 1) if(f[i][j]) {
            fo(k, j, m - 1) if(!(i & a2[k]))
                f[i | a2[k]][k + 1] = (f[i | a2[k]][k + 1] + f[i][j]) % mo;
        }

        int s1 = 0; fo(j, 0, m - 1) s1 += (i & a2[j]) > 0;
        fo(j, 0, m) h[i] += f[i][j];
        h[i] = (h[i] * fu(s1) % mo + mo) % mo;

        ll s = 0;
        fo(j, 0, m - 1) if(i & a2[j])
            s += a[j + 1]; else s += b[j + 1];

        ans = (ans + h[i] * (s % mo)) % mo;
    }
    ans = ans * ksm(c[n][k], mo - 2) % mo;
    printf("%lld", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值