容斥原理笔记
例题
求 中有多少个数能被 中至少一个数字整除。直接将 相加,答案错误。因为 会被计算两次。
我们要将多算的给减掉。显然当 为 的公倍数时,会被多算。所以再减去 即可。
这即是容斥原理。

简单来说就是“奇加偶减”。(其实这么说也不好)
练习
题面
- 给出一个长度为 的序列 ,问在区间 中有多少个数,至少能被 中的一个数整除。
.
根据上文,我们可以容斥。发现如果一个数是集合 的公倍数,那么就会被多(少)计算。
通过 dfs 枚举每一种组合,套用上面的公式即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 19;
int n,k,a[N];
int ans1,ans2;
void dfs(int lvl,int cnt,int mul,int &ans,int n) {
if(lvl>k||mul>n) return ;
dfs(lvl+1,cnt,mul,ans,n);
cnt++,mul=mul*a[lvl]/__gcd(a[lvl],mul);
if(cnt&1) ans+=n/mul;
else ans-=n/mul;
dfs(lvl+1,cnt,mul,ans,n);
}
signed main() {
int T;
cin>>T;
while(T--) {
ans1 = ans2 = 0;
int l,r;
cin>>n>>l>>r;
for(int i=1;i<=n;i++) {
cin>>a[i];
}
k=n;
dfs(1,0,1,ans1,l-1);
dfs(1,0,1,ans2,r);
cout<<ans2-ans1<<"\n";
}
return 0;
}
题面
如果每一次询问都使用多重背包,显然超时。那么不妨弱化问题。假如是没有货币数量的限制呢。
简单的完全背包即可。此时我们要思考 的意义。表示的是使用 块钱的方案数。那么其中包含着一些不合法的,如何才能确定呢?
考虑从转移入手。在多重背包中,一次非法的转移是对于一种货币,面值为 ,有 个,但使用了 个及以上的货币个数。如何书写的呢?
注意, 已经包括了取 种的情况了。
对于一种非法转移,我们已经了解了,但是如果两种都非法呢?被多减去了。
从状态转移开始讨论。, 是非法的。
但是 也是非法的。而 显然都包含 。所以被多减去了。然后套用开头公式解题。
代码
#include<bits/stdc++.h>
using namespace std;
int c[5],d[5],dp[100005],n;
int main() {
for(int i=1;i<=4;i++) cin>>c[i];
cin>>n;
dp[0]=1;
for(int i=1;i<=4;i++) {
for(int j=c[i];j<=100000;j++) {
dp[j]+=dp[j-c[i]];
}
}
while(n--) {
int s;
for(int j=1;j<=4;j++) {
cin>>d[j];
}cin>>s;
int ans=0;
for(int i=0;i<1<<4;i++) {//全 0 的情况会使 ans = dp_s。所以答案就是总共的方案数减去非法方案数。
int p=0,cnt=0;
for(int j=0;j<4;j++) {
if((i>>j) & 1) cnt++, p+=c[j+1]*(d[j+1]+1);
}
if(s-p>=0){
if(cnt & 1) ans-=dp[s-p];
else ans+=dp[s-p];
}
}
cout<<ans<<"\n";
}
return 0;
}
题面
- 武林中流传了 种武功,现在有 个习武之人去学习一些武功,经过深思熟虑,每个人都选择了 个不同的武功,名字叫 。 并学习成功,现在由于武林盟主失踪,群龙无首,这 个人发生了叛乱,任意两个人 和 ,如果存在一种相同的武功他们俩都会,那么他们俩就是朋友,反之他们就是敌人,问 个人中有多少对敌人。
.
我们先要找朋友,并进行计数。再用总数减去朋友,就是敌人。这种思想被称为间接计数。
考虑分类讨论,有些朋友有 种相同的武功,有些是 个
现在有大小为 集合 ,他们都会武功 。那么有 对朋友出现。
那如果有大小为 集合 ,他们都会武功 呢?
同时会两种武功的人肯定也是同时会一种武功的人,他们是有包含关系的。所以套文章开头公式即可,剩下的省略。对于统计是否同时会 种武功,可以二进制枚举与哈希来做,具体就是枚举会的集合,并且哈希再遍历。
本文实现的是比较简单暴力的写法,常数特别大。枚举后用 vector 存到 map 里面遍历。
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define rint register unsigned int
using namespace std;
map<vector<int>,pair<int,int> >mp;
signed main() {
ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
int n; cin>>n;
for(rint i=1;i<=n;i++) {
vector<int>a(5,0);
for(rint j=0;j<5;j++) cin>>a[j];
sort(a.begin(),a.end());
vector<int>t(5,0);
for(rint j=1;j<1<<5;j++) {
rint cnt=0;
for(rint k=0;k<5;k++) {
if((j>>k) & 1) t[k]=a[k],cnt++;
else t[k]=0;
}
sort(t.begin(),t.end());
mp[t].first++,mp[t].second = cnt;
}
}
int ans = 0;
for(auto it:mp) {
auto j = it; int k = j.second.first;
if(j.second.second & 1) ans+= (k-1)*k;
else ans-= (k-1)*k;
}
cout<<((n-1)*n-ans)/2;
return 0;
}

浙公网安备 33010602011771号