容斥原理(CF 547C Mike and Foam & CF 449D Jzzhu and Numbers)

容斥原理:

容斥原理(Inclusion-Exclusion Principle)是组合数学中的一个重要原理,用于计算多个集合的并集的大小。它通过考虑集合的交集来避免重复计数。
(可点击此处进行学习)

CF 547C Mike and Foam

题意:

有 q 次操作,每次输入一个 i ,如果 a[i] 没有被拿出来就将它拿出来,如果已经拿出来了就放回去,然后输出每次操作后,拿出来的数里有多少个对 (a[i], a[j]) 互质

思路:

我们可以将每个数的质因子分解出来,因为\(a[i]<=10^5\),并且,最小的\(7\)个质数相乘大于\(10^5\),所以每个数分解后最多\(6\)个因子,可以直接存下来
用一个数组\(cnt\)\(cnt[i]\)表示拿出来的数中有多少个\(i\)的倍数,每次对一个数进行操作时,枚举\(x\)的因子的组合,通过容斥原理就可以求出与\(x\)不互质的数的个数,再用\(cnt[1]\)减去就是与\(x\)互质的个数。所以就可以得到我们的公式:
\(\Delta \textrm{ans} = \textrm{cnt[1]}+(-1)^1\sum{cnt[p_i]}+(-1)^2\sum{cnt[p_i*p_j]}+\dots\)

具体实现过程见代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
const int MAXN = 500010;
typedef long long ll;
// 2 3 5 7 11 13 17
int a[N];
bool v[N];
ll ans;
vector<int> cnt(MAXN, 0), p[MAXN];
int n, q;

void add(int x, int k){
    int sz = p[x].size();
    for(int i = 0;i < (1 << sz);i ++){
        int temp = 1;
        for(int j = 0;j < sz;j ++)
            if((i >> j) & 1) temp *= p[x][j];

        cnt[temp] += k;
    }
}
void query(int x, int k){
    ll res = 0;
    int sz = p[x].size();
    for(int i = 0;i < (1 << sz);i ++){
        int f = -1, temp = 1;
        for(int j = 0;j < sz;j ++){
            if((i >> j) & 1){
                temp *= p[x][j];
                f *= -1;
            }
        }
        res += f * cnt[temp];
    }
    ans -= res * k;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    for(int i = 2;i < MAXN;i ++)
        if(p[i].empty())
            for(int j = i;j < MAXN;j += i) p[j].push_back(i);

    cin >> n >> q;
    for(int i = 1;i <= n;i ++) cin >> a[i];
    while(q --){
        int op;
        cin >> op;
        if(!v[op]){
            v[op] = true;
            query(a[op], 1);
            add(a[op], 1);
        }else{
            v[op] = false;
            add(a[op], -1);
            query(a[op], -1);
        }
        cout << ans << '\n';
    }
    return 0;
}

CF 449D Jzzhu and Numbers

题意:

从数列中取几个数,要求将它们“&”后得到0,问有多少种取法

思路(来处:Azazel):

(蒟蒻不太会写,原文更加详细)
类似上面的枚举因子,这个题我们枚举一个数在二进制下为 0 的位数,再用上一个叫高维前缀和的
我们不好直接找出符合答案要求的,但是可以找出不符合要求的

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int mod = 1000000007;

const int N = 2000010;
ll p[N];
ll ans = 0;
ll g[N];
ll qpow(ll a, ll b){
    ll res = 1;
    while(b){
        if(b & 1) res = (res * a) % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return res;
}
ll cnt_1(ll x){
    ll res = 0;
    while(x){
        if(x & 1) res++;
        x >>= 1;
    }
    return res;
}
ll mul(ll a,ll b){return a*b % mod;}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    for(int i = 1;i <= n;i ++){
        int x;
        cin >> x;
        p[x] ++;
    }
    for(int j = 0;j < 20;j ++)
        for(int i = (1 << 20) - 1;~i;i --)
            if(!((i >> j) & 1)) p[i] += p[i | (1 << j)];
    for(int i = 0;i < (1 << 20);i ++) g[i] = (qpow(2, p[i]) - 1) % mod;
    for(int i = 0;i < (1 << 20);i ++){
        // --- 这一步取余的操作非常容易忽视 --
        ans = ((ans + mul(g[i], cnt_1(i) & 1 ? -1 : 1)) % mod + mod) % mod;
    }
    cout << ans;
}
posted @ 2025-03-20 17:21  ter_rave  阅读(89)  评论(0)    收藏  举报