算法随笔 - 容斥原理学习

890. 能被整除的数 - AcWing题库

在补牛客多校7的I题I-We Love Strings_2023牛客暑期多校训练营7 (nowcoder.com)时发现处理重复集合用了容斥原理来做,感觉我对容斥原理不太熟悉,因此上网学了学容斥原理。

n个集合的容斥原理的公式为:
∪ i = 1 m S i = S 1 + S 2 + S 3 + S 4 . . . + S m − ( S 1 ∩ S 2 + S 1 ∩ S 3 + . . . + S m − 1 ∩ S m ) + S 1 ∩ S 2 ∩ S 3 + S 1 ∩ S 2 ∩ S 4 . . . + / − . . . \cup_{i=1}^mS_i = S_1 + S_2 + S_3 + S_4 ...+S_m - (S_1 \cap S_2 + S_1 \cap S_3 + ... + S_m-1 \cap S_m) + S_1 \cap S_2 \cap S_3 + S_1 \cap S_2 \cap S_4 ... +/- ... i=1mSi=S1+S2+S3+S4...+Sm(S1S2+S1S3+...+Sm1Sm)+S1S2S3+S1S2S4...+/...
实际上,就是奇数项是加上这个集合的元素,偶数项是减去这个集合的元素。

时间复杂度是:
C n 1 + C n 2 + C n 3 + . . . + C n n C_n^1 + C_n^2 + C_n^3 + ... + C_n^n Cn1+Cn2+Cn3+...+Cnn
经过二项式化简,为O(2^n - 1),因此是O(2^n),指数级别的时间复杂度。

对于容斥原理的大部分题而言,枚举一定会超时的。但是可以用二进制进行优化。对于容斥原理的大部分题而言,n比较小,可以枚举2^n个,在里面套个枚举长度的。

e.g.

for(int k = 1; k < (1 << n); ++k) { // 枚举所有状态,一共2^n
	for(int i = 0; i < n; ++i) { 
		if(k >> i & 1) { // 第i个选的情况
			...
		}
	}
    // 选了奇数/偶数 再进行操作
}

注意:k >> i & 1中的 &

练习题:890. 能被整除的数 - AcWing题库

#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <ctime>
#include <random>
#include <sstream>
#include <numeric>
#include <stdio.h>
#include <functional>
#include <bitset>
#include <algorithm>
using namespace std;

// #define Multiple_groups_of_examples
#define IOS std::cout.tie(0);std::cin.tie(0)->sync_with_stdio(false);
#define dbgnb(a) std::cout << #a << " = " << a << '\n';
#define dbgtt cout<<" !!!test!!! "<<endl;
#define rep(i,x,n) for(int i = x; i <= n; i++)

#define all(x) (x).begin(),(x).end()
#define pb push_back
#define vf first
#define vs second

typedef long long LL;
typedef pair<int,int> PII;

const int INF = 0x3f3f3f3f;
const int N = 2e5 + 21;

void inpfile();
void solve() {
    int n,m; cin>>n>>m;
    vector<int> p(m);
    for(auto &t: p) cin>>t;
    int ans = 0;
    for(int k = 1; k < ( 1 << m); ++k) {
        int cnt = 0; // 选了几个
        int t = 1;
        for(int i = 0; i < m; ++i) {
            if(k >> i & 1) { // 第i个是否可选
                if(1LL * t * p[i] > n) { // 如果相乘大于n,为0,直接break:因为可能有n^m,LL也超,需要大数运算
                    t = -1;
                    break;
                }
                cnt++;
                t *= p[i];
            }
        }
        if(t == -1) continue;
        // 容斥原理:奇数相加,偶数相减
        if(cnt % 2 !=  0) {
            ans += n / t;
        } else ans -= n / t;
        
    }
    cout<<ans;
}
int main()
{
    #ifdef Multiple_groups_of_examples
    int T; cin>>T;
    while(T--)
    #endif
    solve();
    return 0;
}
void inpfile() {
    #define mytest
    #ifdef mytest
    freopen("ANSWER.txt", "w",stdout);
    #endif
}

算法基础(二十八):数学基础 - 容斥原理 - 知乎 (zhihu.com)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

golemon.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值