题号 NC20278
名称 [SCOI2010]幸运数字
来源 [SCOI2010]
题目描述
在中国,很多人都把6和8视为是幸运数字!lxhgww也这样认为,于是他定义自己的“幸运号码”是十进制表示中只包含数字6和8的那些号码,比如68,666,888都是“幸运号码”!
但是这种“幸运号码”总是太少了,比如在[1,100]的区间内就只有6个(6,8,66,68,86,88),于是他又定义了一种“近似幸运号码”。
lxhgww规定,凡是“幸运号码”的倍数都是“近似幸运号码”,当然,任何的“幸运号码”也都是“近似幸运号码”,比如12,16,666都是“近似幸运号码”。 现在lxhgww想知道在一段闭区间[a, b]内,“近似幸运号码”的个数。
样例
输入 1 10 输出 2
输入 1234 4321 输出 809
算法
(dfs + 容斥原理)
这道题刚拿到手,感觉就是常规的数位dp...
题目给出的幸运数不是常规的6或者8的倍数,而是由6和8组成的数,并且还定义了幸运数的倍数是近似幸运数
那么这道题就不能用数位dp来写,而要找其他方法
题目中提示了幸运数很少10000000000内有2046个
根据近似幸运数的定理,现在我们的目标就是从这些幸运数中挑一些数
求他们的最小公倍数,计算这些最小公倍数的倍数的个数
首先这2046个数字中包含了某些数的倍数如666,是6的倍数
666会在枚举6的倍数的时候被枚举到所以可以删去
最后最多剩下943个数字
计算1~n中x的倍数有多少个可以用n / x来计算
计算[l,r]中x的倍数则可以用容斥原理r / x - ( l - 1) / x来求
不过直接计算会重复统计,所以还需要通过奇加偶减的原则(容斥原理)去重
那么现在的问题就是如何找到所有这些最小公倍数
方法就是dfs
虽然最后剩下的幸运数个数很多,但我们将这些数输出出来发现
位数越多的数个数越多,位数越少的数个数越少(根据乘法原理也很容易理解)
于是我们这样dfs,位数层数u,被选择的幸运数个数cnt,最小公倍数s
将递归的层数表示当前枚举到第个去重后的幸运数
由前面已经选择了的数的最小公倍数计算答案(奇加偶减)
我们枚举这个数选或者不选,
不选,层数+1,被选择的幸运数的个数不变,最小公倍数不变
选,层数+1,被选择的幸运数的个数+1,计算当前数与前面数的最小公倍数
由于位数越多的数个数越多,位数越少的数个数越少我们加入一个剪枝
当最小公倍数大于r时直接返回
像是8888888866这样的幸运数,递归两层后就直接反回了
这样的数还有很多所以dfs实际递归次数很少
这里还有一个剪枝,需要将幸运数按照从大到小的顺序排序再进行dfs不然会超时
如果小的数字在高层那么需要递归较深才会被”最小公倍数大于r返回“的剪枝剪掉
如果大的数字在高层那么需要递归一两次就会被剪枝
时间复杂度
参考文献https://blog.nowcoder.net/n/c9b403ebf6284dbcaaa45f0bc54d18c3
C++ 代码
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <unordered_map> #include <map> #include <vector> #include <queue> #include <set> #include <bitset> #include <cmath> #define P 131 #define lc u << 1 #define rc u << 1 | 1 using namespace std; typedef long long LL; const LL N = 10000000000ll; vector<LL> nums,num; LL l,r; LL n; LL ans; void dfs(LL s) { if(s > r) return; if(s != 0) nums.push_back(s); dfs(s * 10 + 6); dfs(s * 10 + 8); } bool cmp(LL &a,LL &b) { return a > b; } void init() { dfs(0ll); for(int i = 0;i < nums.size();i ++) { bool flag = true; for(int j = 0;j < nums.size();j ++) { if(i == j) continue; if(nums[i] % nums[j] == 0) flag = false; } if(flag) num.push_back(nums[i]); } sort(num.begin(),num.end(),cmp); } LL calc(LL x) { return r / x - (l - 1) / x; } void dfs1(int u,int cnt,LL s) { if(s > r) return; if(u >= num.size()) { if(cnt != 0) { if(cnt & 1) ans += calc(s); else ans -= calc(s); } return; } dfs1(u + 1,cnt,s); LL lcm = s / __gcd(s,num[u]); if(lcm <= r / num[u]) dfs1(u + 1,cnt + 1,lcm * num[u]); } void solve() { scanf("%lld%lld",&l,&r); init(); dfs1(0,0,1ll); printf("%lld\n",ans); } int main() { /*#ifdef LOCAL freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #else #endif // LOCAL*/ int T = 1; // init(); // scanf("%d",&T); while(T --) { // scanf("%lld%lld",&n,&m); solve(); // test(); } return 0; }