这道题首先需要注意的是数据范围,两个10^10规模的数相乘会爆long long,所以要尽量避免乘法。
这道题很容易让人想到数位DP,不过这个想法是错误的,这就提示在考试中如果想到一种方法,但是却怎么也想不出来时不要盲目地陷入其中,而要冷静思考有没有其他的解决办法。
这个解决办法就是利用容斥原理。
先dfs出所有的“真正的”幸运数字,然后再利用容斥原理找出近似幸运数字。
为了避免超时,需要一个剪枝,就是如果一个幸运数字是一个比他小的幸运数字的倍数,则这个幸运数字不用考虑,因为后面会统计到。
把处理过的数字从大到小排序,这样可以避免TLE,因为从大的数字开始更有可能大于limit从而跳出dfs。
根据容斥原理,答案就为:每个数的倍数个数-每两个数的公倍数个数+每三个数...这个可以通过传递数的个数来判断。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 3000 + 10;
bool flag[maxn];
long long luck[maxn];
long long cnt = 0,ret = 0;
long long limit,a,b;
void init()
{
freopen("bzoj1853.in","r",stdin);
freopen("bzoj1853.out","w",stdout);
}
bool cmp(const long long &a,const long long &b)
{
return a > b;
}
void dfs(long long num)
{
if(num > limit)return;
if(num != 0)luck[cnt++] = num;
dfs(num * 10 + 6);
dfs(num * 10 + 8);
}
void readdata()
{
scanf("%lld%lld",&a,&b);
}
long long gcd(long long a,long long b)
{
long long t;
while(b != 0)
{
t = a % b;
a = b;
b = t;
}
return a;
}
void work(int step,long long lcm,int tot)
{
if(step == cnt)
{
if(tot)
{
long long tmp = limit / lcm;
if(tot&1)ret += tmp;
else ret -= tmp;
}
return;
}
for(int i = step;i < cnt;i++)
{
long long tmp = gcd(lcm,luck[step]);
if((double)lcm / tmp > (double)limit / luck[step])return;
tmp = lcm * luck[step] / tmp;
work(i + 1,tmp,tot + 1);
}
}
long long calc(long long x)
{
if(x < 6)return 0;
ret = 0,cnt = 0;
limit = x;
dfs(0);
stable_sort(luck,luck + cnt);
for(int i = 0;i < cnt;i++)
{
flag[i] = true;
for(int j = 0;j < i;j++)
{
if(luck[i] % luck[j] == 0)flag[i] = false;
}
}
int tmp = cnt;
cnt = 0;
for(int i = 0;i < tmp;i++)
{
if(flag[i])luck[cnt++] = luck[i];
}
stable_sort(luck,luck + cnt,cmp);
for(int i = 0;i < cnt;i++)
{
work(i,1,0);
}
return ret;
}
void solve()
{
long long ans = calc(b) - calc(a - 1);
printf("%lld",ans);
}
int main()
{
init();
readdata();
solve();
return 0;
}