容斥原理学习笔记

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

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

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

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

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

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

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

bzoj4361 isn

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

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

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

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

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

sp4191

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

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

容斥的时候不是2的素数次方枚举,而是考虑每一个约数。如果这个约数不存在平方因子(就是一堆素因子乘起来),根据素因子的个数奇减偶加,这里的贡献就是 C ( s u m , 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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值