八 (容斥)
题目描述
八是个很有趣的数字啊。
八=发,八八=爸爸,88=拜拜。
当然最有趣的还是 \(8\) 用二进制表示是 \(1000\)。
怎么样,有趣吧。当然题目和这些都没有关系。
某个人很无聊,他想找出 \([a,b]\) 中能被 \(8\) 整除却不能被其他一些数整除的数。
输入格式
第一行一个数 \(n\) ,代表不能被整除的数的个数。
第二行 \(n\) 个数,中间用空格隔开。
第三行两个数 \(a,b\) ,中间一个空格。
输出格式
一个整数,为 \([a,b]\) 间能被 整除却不能被那 \(n\) 个数整除的数的个数。
样例
样例输入
3
7764 6082 462
2166 53442
样例输出
6378
数据范围与提示
对于 \(30\%\) 的数据, \(1\leqslant n\leqslant 5,1\leqslant a\leqslant b\leqslant 10^5\) 。
对于 \(100\%\) 的数据,\(1\leqslant n\leqslant 15,1\leqslant a\leqslant b\leqslant 10^9\) 。个数全都小于等于 \(10^4\) 大于等于 \(1\) 。
分析
首先考虑没有限制的情况,也就是求 \([a,b]\) 中能被 \(8\) 整除的数的个数,答案容易得到是 \(\frac{8}{r} - \frac{8}{l-1}\) 。接下来我们考虑怎么去除同时也被限制的 \(n\) 个数整除的个数。假如现在限制只有一个,我们设为 \(x\) ,那么我们只需要在上边求出的答案里减去一个 \(\frac{lcm(8,x)}{r} - \frac{lcm(8,x)}{l-1}\) 。这样就把能被 \(8\) 整除但是不能被 \(x\) 整除的数算出来了。如果有多个也是一样的,我们设全集 \(U\) 为限制的那 \(n\) 个数,我们只需要枚举这个全集的子集,每一次对于 \(8\) 和这个子集所有数共同的 \(lcm\) 做的贡献进行加减,而加减的考虑要看集合元素的个数,如果是奇数个,就减去,偶数个就加上。(个人理解是奇数个的时候造成的贡献在偶数个的时候去除重复的贡献,手写几个应该就能看出来,如果不对请大佬指出 \(qaq\) )。然后我们就用正常的状压 \(dp\) 搞一搞就行了。
Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 20;
ll a[maxn];
inline ll gcd(ll a,ll b){
if(b == 0)return a;
return gcd(b,a%b);
}
inline ll lcm(ll a,ll b){
return a * b / gcd(a,b);
}
int main(){
ll ans = 0;
int n;
scanf("%d",&n);
for(int i = 1;i <= n;++i){
scanf("%lld",&a[i]);
}
ll l,r;
scanf("%lld%lld",&l,&r);
ans += r / 8 - (l - 1) / 8;
ll mx = (1 << n) - 1;
for(ll s = 1;s <= mx;++s){
ll LCM = 8;
int cnt = 0;
for(int i = 1;i <= n;++i){
if(s & (1 << (i - 1)))LCM = lcm(LCM,a[i]),cnt++;
}
if(cnt & 1)ans -= r/LCM - (l - 1) / LCM;
else ans += r / LCM - (l - 1) / LCM;
}
printf("%lld\n",ans);
return 0;
}
\(Never\ Give\ Up\)