容斥原理学习笔记

简单来说,容斥原理的核心就是减去不合法的。这个时候可能会多减,再把它加回来。

一个经典的应用是,你有若干个限制,彼此独立。你要求满足这些限制条件下的答案。

采用容斥原理是这样一个思路,先算出没有限制条件下的答案。再算出至少打破一个限制的答案。(就是我们强制要求一个限制满足,剩下的就不管他,因为我们是至少)。
把答案减去,我们又发现我们多减了那些同时打破两个限制的情况。在反过来加上至少打破两个限制的答案。这样一直下去,奇减偶加就可以统计了。我们发现容斥原理可以用2^n的代价,使得原本要同时满足的限制变成了只单独满足有限维,剩下维不管它。这就可以非常方便的计数了。

bzoj1042(有上限的方程的解组数类问题)
完全背包算出f[i]f[i]f[i]表示没有限制条件下凑出iii这个面值的种类数。因为每个询问有一个上限,所以我们强制要求哪一些爆了上限。(这里仍然是至少,所以我们可用f[i−(di+1)∗ci])f[i-(di+1)*ci])f[i(di+1)ci]来转移。

bzoj4665(dp和容斥的结合)
同一种颜色的分配肯定都是一样的,所以按照颜色分类。
f[i][j]f[i][j]f[i][j]表示考虑完前iii类颜色,有至少jjj个人不满足限制的方案数。
假设第iii类颜色有x[i]x[i]x[i]个人。转移就枚举kkk个人强制放在原处,选出来这些人(就是满足这个至少)剩下的人此时就不需要考虑去哪了。

最后统计答案的时候,奇减偶加。对于剩下的那些满足限制的人,要考虑他们的自由组合(颜色彼此是互不区分的。所以是剩余总数的阶乘再除以每一块的大小的阶乘的积)这个阶乘的积可以在dp的时候就算到dp数组里面去。dp时乘一个逆元就好了。

总结:这题还是挺特殊的。它用一个富有容斥特色的东西作为状态。使得状态转移变得简单。

bzoj4361 isn

要点:一个不合法的状态一定是由一个长度多1的不下降子序列转移来的,直接减掉即可。

f[i][j]f[i][j]f[i][j]表示末尾为iii,长度是jjj的非降子序列的方案数。可以通过树状数组O(n2logn)O(n^2logn)O(n2logn)计算出来。g[i]g[i]g[i]表示长度为iii的方案数,可以通过fff的后缀和计算出来。

考虑到哪一位停下来。ans+=jc[n−i]∗g[i]ans+=jc[n-i]*g[i]ans+=jc[ni]g[i]。就是前面的随便选,强制至少选了iii位才达到结果。但是有可能之前就合法了,应该停下来。我们多统计了。 考虑这个要点,所以考虑之前哪一位是多的哪一位。从答案里面减去就好了。

一种个人理解:
jc[n−i]∗g[i]jc[n-i]*g[i]jc[ni]g[i]可以考虑成至少删n−in-ini个删完,再减去至少n−i−1n-i-1ni1个删完的乘以转移过来的方案数i+1i+1i+1。就是恰好删n−in-ini个删完的。

一种理解:
任何一个方案都不可能是另一个的前缀
换句话说:对于长度为iii的子序列,如果操作不合法,那么之前一定是一个长度为i+1i+1i+1的子序列。

sp4191

如果没有限制直接选,答案显然是C(n,4)C(n,4)C(n,4),然后考虑减去不合法的情况。

考虑一个因数,预处理出1到n中每一个数作为因数的时候在原数组中出现的次数,记作sumsumsum。记录一个类似莫比乌斯函数的东西cntcntcnt,表示一个数有多少个互不相同的素因子。如果存在平方因子就是0。

容斥的时候不是2的素数次方枚举,而是考虑每一个约数。如果这个约数不存在平方因子(就是一堆素因子乘起来),根据素因子的个数奇减偶加,这里的贡献就是C(sum,4)C(sum,4)C(sum,4)。为什么不考虑平方因子?以4为例,gcd为它的数显然已经包含在gcd为2的数里面了,容斥仅仅是不同因子间的容斥。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e4 + 5;
int used[N], sum[N], vis[N], flag[N], cnt[N], a[N], mx, n;
inline LL calc(int x) {
    return 1LL * x * (x - 1) * (x - 2) * (x - 3) / 24;
}
int main() {
    while (scanf("%d", &n) != EOF) {
        memset(used, 0, sizeof used);
        memset(vis, 0, sizeof vis);
        memset(cnt, 0, sizeof cnt);
        memset(flag, 0, sizeof flag);
        mx = 0;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            used[a[i]]++;
            mx = max(mx, a[i]);
        }
        for (int i = 2; i <= mx; ++i) {
            sum[i] = 0;
            for (int j = i; j <= mx; j += i) {
                if (used[j]) sum[i] += used[j];
            }
        }
        for (int i = 2; i <= mx; ++i) {
            if (!vis[i]) {
                for (int j = i; j <= mx; j += i) {
                    vis[j] = 1;
                    cnt[j]++;
                    if (flag[j] || j % (i * i) == 0) {
                        flag[j] = 1;
                        cnt[j] = 0;
                    }
                }
            }
        }
        LL ans = calc(n);
        for (int i = 2; i <= mx; ++i) {
            if (!cnt[i] || sum[i] < 4) continue;
            if (cnt[i] & 1) ans -= calc(sum[i]);
            else ans += calc(sum[i]);
        }
        printf("%lld\n", ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值