bzoj1853_容斥原理

6 篇文章 0 订阅
2 篇文章 0 订阅

题目大意: 给出两个数a、b, 求在[a,b]中有多少个数是各位上仅含6和8的数的倍数。


题目要求的是倍数的个数, 我们可以很快的算出来仅含6和8的数有2^10个, 剩下的暴力搜索肯定是行不通的。

而在处理倍数的问题的时候, 有公式num = sum/a + sum/b - sum/lcm(a,b), 表示在[1, sum]间a和b的倍数的总数, 这里就用到了容斥原理的思想:A∪B = A + B - A∩B。 这个公式可以得到推广, 即sum内s[]的倍数的总数sum = Σ(sum/si) - Σ(sum/lcm(si, sj)) + Σ(sum/lcm(si, sj, sk)) + ... +(-?) Σ(sum/lcm(Σsi))。 这样一来, 问题就变成了Q(a,b) = Q(1,b) - Q(1,a), 算法的整体框架就出来了, 但是直接算还是会超时, 因此我们需要对搜索过程进行剪枝优化:

首先, 对最开始的2^10个数进行从大到小的排序并存入s[], 这时候筛选出是原来数倍数的数并进行标记。 在当前数大于b的时候, 显然不成立, 而此后所能搜索到的数都比当前数大, 故可剪枝。

其次, s[]中本来有一些就是原来的数的倍数, 对于这样的数我们并不需要去处理, 直接跳过即可。

问题解决。

#include <cstdio>
#include <algorithm>
#define N 10000
#define ebs 1e-6

using namespace std;

typedef long long LL;
LL a, b, sum, s[N];
int num, flag[N];
LL gcd(LL x, LL y)
{
    if (!y) return x;
    return gcd(y, x%y);
}
bool cmp(LL x, LL y)
{
    return x > y;
}
void dfs(LL x)
{
    if (x > b) return;
    if (x) s[++num] = x;
    dfs(10*x+6);
    dfs(10*x+8);
}//求初始值
void get(int x, int y, LL lcm)
{
    if (x > num) return;
    get(x+1, y, lcm);
    if (flag[x]) return;
    LL mid = s[x] / gcd(s[x], lcm) * lcm;
    if (mid > b || mid < 0) return;//本来mid不会有小于0的情况, 但是考虑到可能溢出成为负值就在这里特判一下。 暂且理解为骗分
    if (y & 1) sum += (a-1) / mid - b / mid;
    else sum += b / mid - (a-1) / mid;
    get(x+1, y+1, mid);
}
void deal()
{
    dfs(0);
    sort(s+1, s+num+1, cmp);
    for (int i = 1; i <= num; ++i)
    for (int j = i + 1; j <= num; ++j)
    if (s[i] % s[j] == 0)
    {
        flag[i] = 1;
        break;
    }//标记
    get(1, 0, 1);
    printf("%lld\n", sum);
}
int main()
{
    scanf("%lld %lld", &a, &b);
    deal();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值