[SCOI2010] 幸运数字

前言

看过 TJ 了, 讲真有点匆忙, 虽然说是讲过的题

思路

首先转化题意, 规定由 6,8 组成的数字是 "幸运数字" , 求 [A,B] 中, 幸运数字的倍数的个数

容易发现 "幸运数字" 是好算的, 打表或者记忆化搜索都行
考虑怎么计算一个集合中数的倍数个数

联想到之前一道题, 考虑容斥原理去做, 这个题还不太需要筛子
具体怎么容斥原理?

你注意到我们这个题可以用差分的想法去做, 只需要考虑 [0,x] 区间中的幸运数字的倍数的个数
枚举集合, 可以知道

f(x)=s=12m1(1)|s|1xlcm(a1,a2,,a|s|)

这样直接做是 22046 的, 啊?

怎么优化
考虑 3 个剪枝:

  • 发现对于两个幸运号码 a,b , 如果 ab , 那么对于所有的 bx 就一定有 ax , 因此这样的 b 是不必要的, 去掉所有这样的 b 后, 还剩下 943 个幸运号码
  • 当前的 lcm 一旦大于 B 就不再继续搜索, 这样复杂度就能大大降低
  • 将预处理出的幸运号码从大到小排序, 使 lcm 能更快地超越上界 B

实现

考虑容斥怎么写, 观察到可以用搜索的方式方便剪枝

#include <bits/stdc++.h>
#define ull unsigned long long
#define lcm(a, b) a / std::__gcd(a, b) * b
const int N = 1e6 + 5;
ull l, r, ans, lucky[N], luc[N];
int cnt1, len;
bool vis[N];

inline void predfs(ull x) {
    if (x > r) return;
    lucky[++cnt1] = x;
    predfs(x * 10 + 6); predfs(x * 10 + 8);
}
inline void dfs(int x, int c, ull w) {
    if (x > len) {
        if (c == 0) return;
        if (c & 1) ans += r / w - (l - 1) / w;
        else ans -= r / w - (l - 1) / w;
        return;
    }
    dfs(x + 1, c, w); // 跳过
    if (lcm(w, luc[x]) <= r) dfs(x + 1, c + 1, lcm(w, luc[x])); // 选择
}
signed main()
{
    scanf("%lld %lld", &l, &r);
    predfs(0);
    std::sort(lucky + 1, lucky + cnt1 + 1);
    for (int i = 2; i <= cnt1; i++) {
        if (vis[i]) continue;
        luc[++len] = lucky[i];
        for (int j = i + 1; j <= cnt1; j++)
            if (lucky[j] % lucky[i] == 0) vis[j] = true;
    }
    std::reverse(luc + 1, luc + len + 1);
    dfs(1, 0, 1);
    printf("%lld", ans);
    return 0;
}

总结

一类: 先找到一个集合, 再求这个集合中的数的倍数的问题, 使用容斥原理方便解决

很大的算法也可以考虑剪枝

容斥是可以用搜索解决的

后话

讲真这个题最难的部分是优化到 2946 之后还有信心能过的

posted @   Yorg  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示