Pty hates prime numbers HDU - 7080(容斥+暴力)

题目链接

题意

  1. 问我们 1~n 中有多少个数不是前 k 个质数中的任意一个质数的倍数。
  2. n <=1e18, k <= 16, 总共有 1e5 组询问。

思路

做法 一(会超时)

  1. 如果我们考虑容斥去做:
  2. 用二进制枚举,设枚举的乘积为 x,去考虑前 k 个质数是否选择, a n s + =   n / x ∗ ( − 1 ) 形 成 x 质 数 的 个 数 ans +=~n/x *(-1)^{形成 x 质数的个数} ans+= n/x(1) x
  3. 单次计算的复杂度为: 2 k ∗ k 2^k * k 2kk, 如果经过预处理复杂度可以达到 2 k 2^k 2k.
  4. 总复杂度为 2^k*m, TLE…

做法 二(一种神奇的方法)

  1. 我们假设 k==8 的时候,前 8 个数相乘为: 2 ∗ 3 ∗ 5 ∗ 7 ∗ 11 ∗ 13 ∗ 17 ∗ 19 = 9699690 2 ∗ 3 ∗ 5 ∗ 7 ∗ 11 ∗ 13 ∗ 17 ∗ 19 = 9699690 235711131719=9699690, 而 9699690 是比较小的,我们可以暴力预处理出 1~9699690 中不是前 8 个质数的倍数的数,设为 sm。
  2. 注意到: [ x 不 是 前 8 个 质 数 的 倍 数 ] = = [ x % 9699690 不 是 前 八 个 数 的 倍 数 ] [x 不是前 8 个质数的倍数]==[x\%9699690 不是前八个数的倍数] [x 8]==[x%9699690]
  3. 那么我们当我们的求 1~n 的答案时候: n 的 a n s = ( n / 9699690 ) ∗ s m + n n 的 ans=(n/9699690)*sm+n%9699690 的答案 n ans=(n/9699690)sm+n
    对于 n%9699690 我们是可以预处理出来的,然后我们 O (1) 出答案了。

做法 三(结合前两种方法,拥有优秀的复杂度)

  1. 结合上面两种做法:
  2. 当 k<=8 的时候随便做。
  3. 当 k> 8 的时候,我们可以对第 9~k 个质数进行容斥,而这个容斥是建立在 n 中的数都不是前 8 个质数的基础上进行容斥的,这个正好可以被第二种做法给快速解决!!!

代码

#include <bits/stdc++.h>
using namespace std;
#define db  double
#define ll  long long
#define Pir pair<int, int>
#define fi  first
#define se  second
#define pb  push_back
#define m_p make_pair
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
/*==========ACMer===========*/
#define ull unsigned ll
const int M = 9699690;                      //前 8 个质数的乘积
ull p[20] = { 0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 };
ll sum[M + 10], sm;            //sm 表示 1~M 中不是前 8 个质数的倍数的数字的个数
vector<ull> a[17], b[17];       //前 k 个质数中选择偶数个形成的数 存放在 a 中,选择奇数个形成的数存放在 b 中


void dfs(int x, ull y, int z, int k)        //由 k 个质数组成的 y,z表示组成 y 的质数的个数
{
    if (x > k)
    {
        if (z == 1)         //由偶数个质数组成
        {
            a[k].pb(y);
        }
        else
        {
            b[k].pb(y);
        }

        return;
    }


    dfs(x + 1, y, z, k);                //不使用第 x 个质数
    dfs(x + 1, y * p[x], -z, k);        //使用第 x 个质数
}

void pre_do()
{
    for (int k = 1; k <= 16; k ++)
    {
        if (k <= 8)                     //用 dfs 去暴力组合出前 k 个质数所能形成的乘积的值
        {
            dfs(1, 1, 1, k);
        }
        else
        {
            dfs(9, 1, 1, k);
        }
    }

    for (int i = 1; i <= 8; i ++)                   //标记1~M 中那些数可以被前 8 个质数整除
        for (int j = p[i]; j <= M; j += p[i])
            sum[j] = 1;

    for (int i = 1; i <= M; i ++)                   //求 1~M 中不能被前 8 个质数整除的前缀和
        sum[i] = sum[i - 1] + (sum[i] == 0);        
    sm = sum[M];
}


int main()
{
    pre_do();
    int T; scanf("%d", &T);
    while (T --)
    {
        ll n, k;
        scanf("%lld %lld", &n, &k);
        ull ans = 0;
        if (k <= 8)
        {
            for (auto x : a[k])
            {
                ans += n / x;
            }
            for (auto x : b[k])
            {
                ans -= n / x;
            }
        }
        else
        {
            for (auto x : a[k])
            {
                ull t = n / x;
                ans += (t / M) * sm + sum[t % M];
            }
            for (auto x : b[k])
            {
                ull t = n / x;
                ans -= (t / M) * sm + sum[t % M];
            }
        }

        printf("%lld\n", ans);
    }


    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值