scoi2010 幸运数字

P2567 [SCOI2010]幸运数字

https://luogu.lohu.info/problem/show?pid=2567

题目描述

在中国,很多人都把6和8视为是幸运数字!lxhgww也这样认为,于是他定义自己的“幸运号码”是十进制表示中只包含数字6和8的那些号码,比如68,666,888都是“幸运号码”!但是这种“幸运号码”总是太少了,比如在[1,100]的区间内就只有6个(6,8,66,68,86,88),于是他又定义了一种“近似幸运号码”。lxhgww规定,凡是“幸运号码”的倍数都是“近似幸运号码”,当然,任何的“幸运号码”也都是“近似幸运号码”,比如12,16,666都是“近似幸运号码”。

现在lxhgww想知道在一段闭区间[a, b]内,“近似幸运号码”的个数。

输入输出格式

输入格式:

输入数据是一行,包括2个数字a和b

输出格式:

输出数据是一行,包括1个数字,表示在闭区间[a, b]内“近似幸运号码”的个数

输入输出样例

输入样例#1:
1 10
输出样例#1:
2

说明

对于30%的数据,保证1<=a<=b<=1000000

对于100%的数据,保证1<=a<=b<=10000000000

容斥原理

直接计算会TLE,因为lcm会爆掉long long

所以2个优化:
1、幸运号码中,若i是j的倍数,去掉i

2、最重要的:计算lcm时,用类型double,判断lcm是否会超过b,不超过b才运算

double:负值取值范围为 -1.7976E+308 到 -4.94065645841246544E-324,正值取值范围为 4.94065645841246544E-324 到 1.797693E+308

#include<cstdio>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
LL a,b,ans,g[10000],lu[10000],tot;
int cnt;
bool v[10000];
LL pre()
{
    g[1]=6;g[2]=8;
    int h;
    for(h=1,cnt=2;g[h]<=b;h++)
    {
        g[++cnt]=g[h]*10+6;
        g[++cnt]=g[h]*10+8;
        while(g[cnt]>b) cnt--;
    }
    for(int i=1;i<=cnt;i++)
     if(!v[i])
      {
         lu[++tot]=g[i];
         for(int j=i+1;j<=cnt;j++)
          if(g[j]%g[i]==0)
              v[j]=true;
      }
}
LL gcd(LL x,LL y)
{
    return y ? gcd(y,x%y):x;
}
inline void dfs(int now,LL num,int sum)
{
    if(now==0)
    {
        LL s=b/num-a/num;
        if(sum&1) ans-=s;
        else ans+=s;
        return;
    }
    dfs(now-1,num,sum);
    double tmp=num/gcd(num,lu[now]);
    //注意这里不能写成  double tmp=num/gcd(num,lu[now])*lu[now],然后if(tmp<=b)
    //因为num/gcd(num,lu[now])仍是long long类型,再乘可能爆了 
    if(tmp*lu[now]<=b)  dfs(now-1,num/gcd(num,lu[now])*lu[now],sum+1);
}
int main()
{
    cin>>a>>b;
    pre();
    --a;
    ans=b-a;//为什么ans初值不是0 
    //因为在dfs时,会dfs到一个数都不选,此时lcm=1,ans要减去 b/1-a/1,所以这里提前加上 
    dfs(tot,1,1);
    cout<<ans;
}

想到了容斥原理,但没想到要去掉倍数

想到了lcm会爆long long,要用高静,弃疗了,没想到用double先判断,超过b的不运算

原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/6624116.html