#搜索,容斥#洛谷 2567 [SCOI2010]幸运数字 题目 分析 代码

问区间([l,r],l,rleq 10^{10})中有多少个数是

数位由6或8组成的数的倍数(包括本身)


分析

数位由6或8组成的数最多有两千多种,
这可以直接一遍暴搜得到
对于区间([1,n])答案应该是(frac{n}{i}-frac{n}{i*j}+frac{n}{i*j*k})
当选出的数的最小公倍数超过(n)可以剪枝,不会达到上界,
但是这样远远不够,考虑若(i|j)那么(j)显然可以直接舍弃,剩下的数可以降序排序后剪枝
然后对于超过(n/2)的数被选择只能选择自己(两数gcd至少为2),可以提前处理,这样就可以AC了


代码

#include <cstdio>
#include <algorithm>
#define rr register
using namespace std;
typedef long long lll; bool v[2111];
lll b[2111],l,r,ans,sum,now,m;
inline void Pro(lll now,lll n){
	if (now>n) return;
	b[++b[0]]=now;
	Pro(now*10+6,n),
	Pro(now*10+8,n);
}
inline lll gcd(lll x,lll y){return y?gcd(y,x%y):x;}
inline void dfs(int dep,lll lcm,int cnt){
	if (dep>m) return;
	dfs(dep+1,lcm,cnt);
	rr lll t=lcm*b[dep]/gcd(lcm,b[dep]);
	if (t>now) return;
	if (cnt&1) ans-=now/t;
	    else ans+=now/t;
	dfs(dep+1,t,cnt+1);
}
inline lll answ(lll n){
	if (!n) return 0;
	ans=0,now=n,dfs(1,1,0);
	return ans;
}
signed main(){
	scanf("%lld%lld",&l,&r),Pro(6,r),Pro(8,r);
	sort(b+1,b+1+b[0]);
	for (rr int i=1;i<=b[0];++i) if (!v[i])
	for (rr int j=i+1;j<=b[0];++j) if (b[j]%b[i]==0) v[j]=1;
	for (rr int i=1;i<=b[0];++i)
	if (!v[i]){
		if (b[i]>r/2) sum+=r/b[i]-(l-1)/b[i];
		    else b[++m]=b[i];
	}
	reverse(b+1,b+1+m);
    return !printf("%lld",sum+answ(r)-answ(l-1));
}