BZOJ 1853 - [Scoi2010]幸运数字
Published on 2016-06-12描述
定义幸运数字是其十进制表示中只含有 6, 8 的数,求 内能被幸运数字整除的数。
分析
我们考虑极限情况,估算一下规模:。
这样总共有 个幸运数字。显然,如果一个幸运数字能被另外一个幸运数字整除,那么这个幸运数字就无用。去掉这种情况后有 个数。
根据容斥原理,能被 中任意一个整除的数的个数为 能被 整除的数 + 能被 整除的数 - 能被 整除的数。
而我们在这里有 943 个数,有 种数,是不是不能用容斥原理呢?
可以用。因为只要 LCM 超过了 ,后面完全可以不考虑,那么我们可以将数从大到小排序,用 DFS 来计算容斥,一旦大于 就立刻剪枝。这样时间是可以承受的。
为什么不能从小到大排序,实际有用的乘积不是一样的吗?对,虽然是一样的,但是我们的剪枝次数却变多了,导致会慢很多。
Trick:计算 LCM 判断大于 的时候有可能爆 LL,所以可以转换成 double 计算。
代码
// Created by Sengxian on 6/12/16. // Copyright (c) 2016年 Sengxian. All rights reserved. // BZOJ 1853 容斥原理 + DFS 剪枝 #include <bits/stdc++.h> using namespace std; typedef long long ll; vector<ll> a, b; vector<bool> flag; ll l, r, ans = 0; ll gcd(ll x, ll y) { return y == 0 ? x : gcd(y, x % y); } void dfs(int k, int sign, ll lcm) { if (k == (int)b.size()) { if (lcm > 1) ans += sign * (r / lcm - (l - 1) / lcm); return; } dfs(k + 1, sign, lcm); ll tmp = b[k] / gcd(b[k], lcm); if ((long double)tmp * lcm > r) return; //从大往小选择,这样就可以很快超过 r,被剪枝。 dfs(k + 1, -sign, lcm * tmp); } ll solve() { queue<ll> q; q.push(6LL), q.push(8LL); while (!q.empty()) { ll now = q.front(); q.pop(); a.push_back(now); if (now * 10 + 6 <= r) q.push(now * 10 + 6); if (now * 10 + 8 <= r) q.push(now * 10 + 8); } sort(a.begin(), a.end()); flag = vector<bool>(a.size()); for (int i = 0; i < (int)a.size(); ++i) for (int j = i + 1; j < (int)a.size(); ++j) if (a[j] % a[i] == 0) flag[j] = true; for (int i = 0; i < (int)a.size(); ++i) if (!flag[i]) b.push_back(a[i]); reverse(b.begin(), b.end()); ans = 0; dfs(0, -1, 1); return ans; } int main() { #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif scanf("%lld%lld", &l, &r); printf("%lld\n", solve()); return 0; }