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

Description:

这里写图片描述

题解:

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

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

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

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

f g 的关系是 g ( S ) = S S f ( S )

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

f ( S ) = S S g ( S ) h ( S )

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

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

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

暴力枚举是会超时的,所以用宽搜+状压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);
}

猜你喜欢

转载自blog.csdn.net/Cold_Chair/article/details/81809492