4和7是一个幸运数字

一个比较有趣的面试题

题意:

仅由4和7组成的十进制数字被称为幸运数字,如,4,7,44,47。

那么仅由幸运数字乘积得到的数字为超级幸运数字,如,28 = 4 * 7。

现在给你两个数字表示上下限,让你求这段区间内有多少个超级幸运数字。总共有1000组查询,上下限最多到 10 12

思路:

第一想法是容斥搞一下,但是其实非常难,因为除了爆搜很难去构造出仅由幸运数字乘起来得到的数。
第二想法是爆搜,既然是面试题,肯定不会难到哪里去。那就估算一下复杂度和爆搜的体量。

幸运数字的量:其实可以把4和7当做0和1,用二进制估算,大约是 2 13 个,也就是不超过 8192 。因此这部分可以轻而易举地预处理一下。

超级幸运数字的量:
首先,我们可以发现,4的幂全都是超级幸运数字,那么在 10 12 里,大致有19个4的幂。那么也就是说,通过其他途径进行组合的,每一列不会超过20个。

可以这么想,你拿着这个去做类似一个 2 8192 个的组合,然后就会发现,这样的搜索树,经过剪枝之后,几乎变成了一条链,而且被剪枝的搜索树是以幂次的下降的。所以绝对不会超过 2 20 个超级幸运数。这个推理是没有根据的,但是 8192 l o g 2 ( 8192 ) 这个量级的,所以总体也不是特别大。

所以就可以直接爆搜了,贴一个预处理完的代码。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

long long p[10000];
int num = 0;

void init() {
    for (int n = 1; n <= 12; n ++) {
        for(int s = 0; s < (1 << n); s ++) {
            long long t = 0;
            for (int k = 0; k < n; k ++) {
                if (s>>(n-k-1)&1) {
                    t = t * 10 + 7;
                } else {
                    t = t * 10 + 4;
                }
            }
            p[num ++] = t;
        }
    }
}

std::vector<long long> q;

long long solve(int pos, long long now, long long n) {
    if (pos >= num) {
        return 0;
    }
    int ans = 0;
    if (p[pos] <= n / now) {
        ans ++;
        //q.push_back(now * p[pos]);
        q.emplace_back(now * p[pos]);
        ans += solve(pos, now * p[pos], n);
        ans += solve(pos + 1, now, n);
    }
    return ans;
}

void print() {
    for (int i = 0; i < num; i ++) {
        printf("%lld%c", p[i], " \n"[i == num - 1]);
    }
}

int main() {
    init();
//    print();
    long long n = 1000000000000L;
    long long ans = solve(0, 1, n);
    printf("%d %d %lld\n", num, (int)q.size(), ans);
    sort(q.begin(), q.end());
    q.erase(unique(q.begin(), q.end()), q.end());
    printf("%d %d %lld\n", num, (int)q.size(), ans);
}

bzoj1853

[Scoi2010]幸运数字 dp+容斥原理

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

【样例输入1】
1 10
【样例输入2】
1234 4321
Sample Output

【样例输出1】
2
【样例输出2】
809
Hint

【数据范围】
对于30%的数据,保证1 < =a < =b < =1000000
对于100%的数据,保证1 < =a < =b < =10000000000

显然容斥,剩下的就是黑科技的问题了。
容斥的话,就是我们首先预处理出来所有的幸运数字,然后筛一遍,筛掉所有是其他幸运数字倍数的数避免重复计算。
然后就是选1个-选2个的lcm+选3个的lcm-….
一个dfs搞定即可。
但是需要注意,dfs内部的lcm我们不可以求出来,因为会爆long long ,是的我也不知道为什么。
所以我们需要转成double 型比较是否越界。
另外,我们筛完之后的那些数字最好按照从大到小的顺序排,如果从小到大的话常数大一点点,但就是这么一点点你就过不去,貌似是从大到小的话更能够对一些非法状态早排除吧

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm> 
#define ll long long 
#define N 10001
using namespace std;

ll l,r;
int t,n,m;
ll ans;
ll a[N],b[N];
bool vis[N];

ll gcd(ll a,ll b)
{
    return b?gcd(b,a%b):a;
}
void pre(int x,ll y)
{
    if(y>r)return;
    if(x>0)a[++m]=y;
    pre(x+1,y*10+6);
    pre(x+1,y*10+8);
}
void dfs(int x,int y,ll z)
{
    if(x>n)
    {
        if(y&1)ans+=r/z-(l-1)/z;
        else if(y)ans-=r/z-(l-1)/z;
        return;
    }
    dfs(x+1,y,z);
    ll tmp=z/gcd(a[x],z);
    if(((double)a[x]*tmp)<=r) dfs(x+1,y+1,a[x]*tmp);
}
int main()
{
    scanf("%lld%lld",&l,&r);
    pre(0,0);
    sort(a+1,a+m+1);
    for(int i=1;i<=m;i++)
       if(!vis[i])
       {
              b[++n]=a[i];
              for(int j=i+1;j<=m;j++)
                  if(!(a[j]%a[i]))
                      vis[j]=1;
       }
    for(int i=1;i<=n;i++)
       a[n-i+1]=b[i];
    dfs(1,0,1);
    printf("%lld",ans);
}

猜你喜欢

转载自blog.csdn.net/u013007900/article/details/79811354