P2567 [SCOI2010]幸运数字 [容斥+有技巧的搜索]

本文探讨了在指定区间内寻找“近似幸运号码”的算法。通过对“幸运号码”(仅由数字6和8组成的数)及其倍数的定义,采用优化后的深度优先搜索策略,实现了对“近似幸运号码”计数的有效计算。文章详细介绍了算法的设计思路,包括排序、剪枝和容斥原理的应用,以及在实现过程中需要注意的细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

幸 运 数 字 幸运数字


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

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


最 初 想 法 最初想法
预处理出所有的 幸运数字, 存到 A [ ] A[] A[] 数组里, 共有 c n t cnt cnt 个.

[ 1 , x ] [1,x] [1,x]含有 F [ i ] F[i] F[i] 个第 i i i 个幸运数字的倍数,
枚举 A i A_i Ai 的倍数, 若 存在 A i ∣ A j A_i|A_j AiAj, 则 F [ i ] − = F [ j ] F[i]-=F[j] F[i]=F[j].
最后 A n s = ∑ i = 1 c n t F [ i ] Ans=\sum_{i=1}^{cnt}F[i] Ans=i=1cntF[i].

但是这个算法连样例都过不去.

上方算法好蠢 …

错因: 存在 L c m ( A i , A j ) ∣ A i Lcm(A_i,A_j)|A_i Lcm(Ai,Aj)Ai, 但是 由于 L c m ( A i , A j ) Lcm(A_i,A_j) Lcm(Ai,Aj) 并不一定在 A [ ] A[] A[] 中, 于是 F [ i ] F[i] F[i] 没有减去 L c m ( A i , A j ) Lcm(A_i,A_j) Lcm(Ai,Aj) 对应的 F F F 值 .


正 解 部 分 正解部分
老实地 容斥, 大力地 剪枝 .

  • 优 化 1 : 按 从 大 到 小 排 序 , 使 得 D F S 尽 早 b r e a k . 优化1: 按从大到小排序,使得DFS尽早break. 1:,使DFSbreak.
  • 优 化 2 : 对 于 A i ∣ A j , 去 掉 A j 无 影 响 优化2: 对于A_i|A_j,去掉A_j无影响 2:AiAj,Aj.
  • 优 化 3 : 对 于 A i > b / 3 , 其 无 法 与 其 它 数 字 结 合 , 直 接 计 入 答 案 \color{red}{优化3:对于A_i>b/3, 其无法与其它数字结合, 直接计入答案} 3:Ai>b/3,,

  • 剪 枝 1 : 若 当 前 的 L c m 大 于 右 端 点 ,   r e t u r n 剪枝1: 若当前的 Lcm 大于右端点,\ return 1:Lcm, return

注意 D F S DFS DFS出来的 A [ ] A[] A[]数组并不是有序的, 这是因为 D F S DFS DFS 的回溯过程使得数据梯度出现向下的陡坡.

求解 L c m Lcm Lcm 时要小心爆 l o n g   l o n g long\ long long long, 要转化为 d o u b l e double double 进行运算 .

总结: 对暴搜的优化主要包括
  1. 使搜索规模消减: 体现在 优化2,3 .
  2. 使搜索过程尽早结束, 体现在 优化1,剪枝1 .

实 现 部 分 实现部分
没什么好说的 …

#include<cstdio>
#include<algorithm>
#define reg register
typedef long long ll;

const int maxn = 40005;

int cnt;

ll a;
ll b;
ll Tmp_1;
ll A[maxn];
ll B[maxn];

bool Used[maxn];

ll Gcd(ll a, ll b){ return !b?a:Gcd(b, a%b); }
ll Lcm(ll a, ll b){ return a/Gcd(a, b) * b; }

void DFS(ll sum){
        if(sum > b) return ;
        if(sum) A[++ cnt] = sum;
        DFS(sum*10 + 6);
        DFS(sum*10 + 8);
}

void DFS_2(int k, bool opt, ll sum){
        if(k == cnt+1){
                if(sum == 1) return ;
                if(opt & 1) Tmp_1 += b/sum - (a-1)/sum;
                else Tmp_1 -= b/sum - (a-1)/sum;
                return ;
        }
        if(sum > b) return ;
        DFS_2(k+1, opt, sum);
        sum = sum/Gcd(sum, B[k]);
        if((double)sum*B[k] <= b) DFS_2(k+1, opt^1, sum*B[k]);
}

int main(){
        scanf("%lld%lld", &a, &b);
        DFS(0);
        std::sort(A+1, A+cnt+1);
        for(reg int i = 1; i <= cnt; i ++){
                if(Used[i]) continue ;
                for(reg int j = i+1; j <= cnt; j ++)
                        if(A[j]%A[i] == 0) Used[j] = 1;
        }
        int tmp = 0;
        for(reg int i = cnt; i >= 1; i --){
                if(Used[i]) continue ;
                if(A[i] > b/3) Tmp_1 += b/A[i] - (a-1)/A[i];
                else B[++ tmp] = A[i];
        }
        cnt = tmp;
        DFS_2(1, 0, 1);
        printf("%lld\n", Tmp_1);
        return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值