幸运数字


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.
  • <mstyle mathcolor="red"> 3 : A i &gt; b / 3 , , </mstyle> \color{red}{优化3:对于A_i&gt;b/3, 其无法与其它数字结合, 直接计入答案} 3:Ai>b/3,,

  • 1 : L c m , <mtext>   </mtext> 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 <mtext>   </mtext> 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;
}