题目大意: 给出两个数a、b, 求在[a,b]中有多少个数是各位上仅含6和8的数的倍数。
题目要求的是倍数的个数, 我们可以很快的算出来仅含6和8的数有2^10个, 剩下的暴力搜索肯定是行不通的。
而在处理倍数的问题的时候, 有公式num = sum/a + sum/b - sum/lcm(a,b), 表示在[1, sum]间a和b的倍数的总数, 这里就用到了容斥原理的思想:A∪B = A + B - A∩B。 这个公式可以得到推广, 即sum内s[]的倍数的总数sum = Σ(sum/si) - Σ(sum/lcm(si, sj)) + Σ(sum/lcm(si, sj, sk)) + ... +(-?) Σ(sum/lcm(Σsi))。 这样一来, 问题就变成了Q(a,b) = Q(1,b) - Q(1,a), 算法的整体框架就出来了, 但是直接算还是会超时, 因此我们需要对搜索过程进行剪枝优化:
首先, 对最开始的2^10个数进行从大到小的排序并存入s[], 这时候筛选出是原来数倍数的数并进行标记。 在当前数大于b的时候, 显然不成立, 而此后所能搜索到的数都比当前数大, 故可剪枝。
其次, s[]中本来有一些就是原来的数的倍数, 对于这样的数我们并不需要去处理, 直接跳过即可。
问题解决。
#include <cstdio>
#include <algorithm>
#define N 10000
#define ebs 1e-6
using namespace std;
typedef long long LL;
LL a, b, sum, s[N];
int num, flag[N];
LL gcd(LL x, LL y)
{
if (!y) return x;
return gcd(y, x%y);
}
bool cmp(LL x, LL y)
{
return x > y;
}
void dfs(LL x)
{
if (x > b) return;
if (x) s[++num] = x;
dfs(10*x+6);
dfs(10*x+8);
}//求初始值
void get(int x, int y, LL lcm)
{
if (x > num) return;
get(x+1, y, lcm);
if (flag[x]) return;
LL mid = s[x] / gcd(s[x], lcm) * lcm;
if (mid > b || mid < 0) return;//本来mid不会有小于0的情况, 但是考虑到可能溢出成为负值就在这里特判一下。 暂且理解为骗分
if (y & 1) sum += (a-1) / mid - b / mid;
else sum += b / mid - (a-1) / mid;
get(x+1, y+1, mid);
}
void deal()
{
dfs(0);
sort(s+1, s+num+1, cmp);
for (int i = 1; i <= num; ++i)
for (int j = i + 1; j <= num; ++j)
if (s[i] % s[j] == 0)
{
flag[i] = 1;
break;
}//标记
get(1, 0, 1);
printf("%lld\n", sum);
}
int main()
{
scanf("%lld %lld", &a, &b);
deal();
return 0;
}