COGS 513 八
513. 八
http://www.cogs.pro/cogs/problem/problem.php?pid=513
★☆ 输入文件:eight.in
输出文件:eight.out
简单对比
时间限制:1 s 内存限制:128 MB
【问题描述】
八是个很有趣的数字啊。八=发,八八=爸爸,88=拜拜。当然最有趣的还是8用二进制表示是1000。怎么样,有趣吧。当然题目和这些都没有关系。
某个人很无聊,他想找出[a,b]中能被8整除却不能被其他一些数整除的数。
【输入文件】
第一行一个数n,代表不能被整除的数的个数。第二行n个数,中间用空格隔开。第三行两个数a,b,中间一个空格。
【输出文件】
一个整数,为[a,b]间能被8整除却不能被那n个数整除的数的个数。
【样例输入】
eight.in
3
7764 6082 462
2166 53442
【样例输出】
eight.out
6378
【数据规模】
对于30%的数据, 1≤n≤5,1≤a≤b≤100000。
对于100%的数据,1≤n≤15,1≤a≤b≤10^9,N个数全都小于等于10^4大于等于1。
容斥原理
全集Z=能被8整除的数
性质p1为能被n1整除,p2为不能被8整除,能被n2整除……
ans=Z中不包含任意性质p的数
这些数都有一个前提:在Z中,所以最小公倍数初值为8
dfs过程中遇到的障碍:
求好几个数的lcm时,
没有必要for枚举几个数,在枚举哪几个数
容斥原理所有项的加加减减都是集合的所有子集
所以dfs时只需分这个数加不加入这个子集即可
这样计算n个数的lcm时,还可利用计算的n-1个数的lcm结果
递归式写法:
#include<cstdio> using namespace std; int n,ans; long long a,b,f[10001]; int cal(long long m) { return b/m-(a-1)/m; } long long gcd(long long x,long long y) { return y ? gcd(y,x%y):x; } long long lcm(long long x,long long y) { return x*y/gcd(x,y); } void dfs(int now,long long num,int sum) { if(now==n+1) { int s=cal(num); if(sum%2) ans-=s; else ans+=s; return; } long long next=lcm(num,f[now]); dfs(now+1,next,sum+1); dfs(now+1,num,sum); } int main() { freopen("eight.in","r",stdin); freopen("eight.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lld",&f[i]); scanf("%lld%lld",&a,&b); dfs(1,8,0); printf("%d",ans); }
非递归写法:
利用二进制生成子集
#include<cstdio> using namespace std; int n,ans; long long a,b,f[10001]; int cal(long long m) { return b/m-(a-1)/m; } long long gcd(long long x,long long y) { return y ? gcd(y,x%y):x; } long long lcm(long long x,long long y) { return x*y/gcd(x,y); } int main() { freopen("eight.in","r",stdin); freopen("eight.out","w",stdout); scanf("%d",&n); for(int i=0;i<n;i++) scanf("%lld",&f[i]); scanf("%lld%lld",&a,&b); int sum=1<<n,cnt; ans=b/8-(a-1)/8; long long lcmm; for(int i=1;i<sum;i++) { cnt=0;lcmm=8; for(int j=0;j<n;j++) if(i&(1<<j)) lcmm=lcm(lcmm,f[j]),cnt++; if(cnt&1) ans-=b/lcmm-(a-1)/lcmm; else ans+=b/lcmm-(a-1)/lcmm; } printf("%d",ans); }