在补牛客多校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−(S1∩S2+S1∩S3+...+Sm−1∩Sm)+S1∩S2∩S3+S1∩S2∩S4...+/−...
实际上,就是奇数项是加上这个集合的元素,偶数项是减去这个集合的元素。
时间复杂度是:
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
中的&
#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
}