容斥原理和容斥DP

容斥原理和容斥DP

容斥原理

∣ ⋂ i = 1 n S i ‾ ∣ = ∣ U ∣ − ∣ ⋃ i = 1 n S i ∣ = ∑ 0 ≤ k ≤ n ( − 1 ) k ∑ 1 ≤ i 1 < ⋯ < i k ≤ n ∣ ⋂ j = 1 k S i j ∣ \left|\bigcap_{i=1}^n \overline{S_i}\right| = |U| - \left|\bigcup_{i=1}^n S_i\right| = \sum_{0 \le k\le n}(-1)^k\sum_{1\le i_1<\cdots<i_k\le n}\left|\bigcap_{j=1}^k S_{i_j}\right| i=1nSi=Ui=1nSi=0kn(1)k1i1<<iknj=1kSij

∣ A ∪ B ∣ = ∣ A ∣ + ∣ B ∣ − ∣ A ∩ B ∣ ∣ A ∩ B ∣ = ∣ A ∣ + ∣ B ∣ − ∣ A ∪ B ∣ |A \cup B| = |A| + |B| - |A \cap B| \\ |A \cap B| = |A| + |B| - |A \cup B| AB=A+BABAB=A+BAB

少的加上,多的减去,简称奇加偶减。

例题

P1450

那么怎么办呢?如果没有硬币数量的限制那就多好啊?直接一个完全背包预处理,然后O(1)输出就好了

可是有了硬币的限制怎么办?我们先考虑一个简单一点的情况:只有第一个硬币有限制。

如果我们用类似前缀和的思想(术语叫差分),我们先完全背包预处理好无限制的情况,拿dp[tot]减去dp[tot-c[i]*(d[i]+1)]就是我们所需的方案数。

这是为什么呢?为什么要弄个c[i]*(d[i]+1)?其实我们可以这样想,无限制的情况就是没有那个di,而有限制时,不应该计入答案的方案数就是把c[i]这个硬币取了超过d[i]次,对吧?那么我们手动先取出d[i]+1个c[i]的硬币,然后剩下的价值弄个完全背包,这时就是我们所不需要的答案, 把它减掉就行了。

那么对于4个(或更多)的硬币有限制,我们就逐一把4个硬币单独限制的方案数减掉,这时可能会减重了(即同时两个硬币有限制的情况减了两次),所以我们再把4个硬币两两同时限制的方案数加上,可能又加重了,再把4个硬币33同时限制减掉,最后加上4个同时限制的方案数就是我们所需的答案。这就是大名鼎鼎的容斥原理啊!写成位运算就很优美了!

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out1.txt", "w", stdout)

#define MOD 1000000007

#define MAXT 100005

ll C[10];
ll D[10];

ll dp[MAXT];

int main()
{
    for (int i = 1; i <= 4; i++)
    {
        scanf("%lld", C + i);
    }

    dp[0] = 1;
    for (int i = 1; i <= 4; i++)
    {
        for (int w = C[i]; w < MAXT; w++)
        {
            dp[w] += dp[w - C[i]];
        }
    }

    int T;
    scanf("%d", &T);

    while (T--)
    {
        for (int i = 1; i <= 4; i++)
        {
            scanf("%lld", D + i);
        }
        ll S;
        scanf("%lld", &S);

        ll ans = 0;

        for (int b = 0; b <= 15; b++)
        {
            ll t = S;
            int cnt = 0;

            for (int i = 0; i <= 3; i++)
            {
                if ((b >> i) & 1)
                {
                    t -= C[i + 1] * (D[i + 1] + 1);
                    cnt = 1 - cnt;
                }
            }

            if(t < 0) continue;

            if (cnt)
            {
                ans -= dp[t];
            }
            else
            {
                ans += dp[t];
            }
        }

        printf("%lld\n", ans);
    }
    return 0;
}

P5664

选不能超过一半的行,我们枚举超过一半列的情况即可,用总的方案数减去,此题不用容斥原理是因为超过一半列只能有一列,各种情况互不相容,因此直接减即可。

#include <bits/stdc++.h>

using namespace std;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)

#define MOD 998244353

typedef long long ll;

ll dp1[105][4500];

ll dp2[105][105];

ll matrix[105][2005];
ll psum[105];
int n, m;

int main()
{
    scanf("%d %d", &n, &m);

    for (int r = 1; r <= n; r++)
    {
        for (int c = 1; c <= m; c++)
        {
            scanf("%lld", &matrix[r][c]);
            matrix[r][c] %= MOD;
            psum[r] = (psum[r] + matrix[r][c]) % MOD;
        }
    }

    dp2[0][0] = 1;
    for (int r = 1; r <= n; r++)
    {
        dp2[r][0] = 1;
        for (int k = 1; k <= r; k++)
        {
            dp2[r][k] = (dp2[r - 1][k] + (psum[r] * dp2[r - 1][k - 1]) % MOD) % MOD;
        }
    }

    ll del = 0;
    for (int c = 1; c <= m; c++)
    {
        memset(dp1, 0, sizeof(dp1));
        dp1[0][n] = 1;
        for (int r = 1; r <= n; r++)
        {
            for (int j = n - r; j <= n + r; j++)
            {
                dp1[r][j] = ((
                                 dp1[r - 1][j] +
                                 (((psum[r] - matrix[r][c]) % MOD + MOD) % MOD * dp1[r - 1][j + 1]) % MOD) %
                                 MOD +
                             (matrix[r][c] * dp1[r - 1][j - 1]) % MOD) %
                            MOD;
            }
        }
        for (int j = 1; j <= n; j++)
        {
            del = (del + dp1[n][j + n]) % MOD;
        }
    }

    ll total = 0;
    for (int k = 1; k <= n; k++)
    {
        total = (total + dp2[n][k]) % MOD;
    }

    printf("%lld", (((total - del) % MOD) + MOD) % MOD);
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值