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

Description:

这里写图片描述

题解:

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

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

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

显然 g(S) g ( S ′ ) 是非常好算的,记 cnt c n t 表示拼盘里的水果都属于S’集的拼盘的个数,则 g(S)=Ckcnt g ( S ′ ) = C c n t k

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

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

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

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

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

使 h(S)=(1)|S| h ( S ′ ) = ( − 1 ) | S ′ | ,再把求出的 f(S) f ( S ) 也乘上 (1)|S| ( − 1 ) | S | 既可以得到 f f <script type="math/tex" id="MathJax-Element-20">f</script>,后面就简单了。

暴力枚举是会超时的,所以用宽搜+状压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);
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值