BZOJ 3944: Sum (杜教筛模板)

题目传送门


题目分析

杜教筛模板题,人生中第一道杜教筛。

在这里推荐一篇非常棒的文章。【skywalkert’s space
相信大多数人都是从这里开始了解和学习杜教筛的。

解题方法我就不一条公式一条公式的敲进去了,直接引用该文章中的片段:

这里写图片描述

这里写图片描述

其实杜教筛就分为两个主要部分,一个是在所有询问之前的线性筛,预处理出 n23 n 2 3 的表。另一个部分就是在dfs中进行分块计算,同时用哈希map记忆化搜索。

ps:用hash一般比用map要快,我采用手写hash的做法,代码会略长(我就没写过几次hash)。
还有,一开始我RE到死,原因竟然是有几个地方爆了int(谁叫我作死开int),要注意处理强转的细节,或者多用long long。


代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
#define N 1000010
#define M 2333333

using namespace std;

typedef long long LL;
int T, n, cnt;
bool vis[N];
int miu[N], prime[N];
LL phi[N];

int hash_n1[M], hash_n2[M], hash_v2[M];
LL hash_v1[M];

void Da(){
    miu[1] = 1;
    vis[1] = true;
    phi[1] = 1ll;
    for(int i = 2; i < N; i++){
      if(!vis[i]){
        prime[++cnt] = i;
        miu[i] = -1;
        phi[i] = (LL)(i-1);
      }
      for(int j = 1; j <= cnt && i * prime[j] < N; j++){
        vis[i * prime[j]] = true;
        if(i % prime[j] == 0){
          miu[i * prime[j]] = 0;
          phi[i * prime[j]] = phi[i] * (LL)prime[j];
          break;
        }
        else{
          miu[i * prime[j]] = -miu[i];
          phi[i * prime[j]] = phi[i] * (LL)(prime[j]-1);
        }
      }
    }
    for(int i = 1; i < N; i++)  phi[i] += phi[i-1], miu[i] += miu[i-1];
}

LL Find1(int x){
    int p = x % M;
    while(hash_n1[p] && hash_n1[p] != x)  p = (p+1) % M;
    if(!hash_n1[p])  return -1;
    return hash_v1[p];
}

int Find2(int x){
    int p = x % M;
    while(hash_n2[p] && hash_n2[p] != x)  p = (p+1) % M;
    if(!hash_n2[p])  return -1;
    return hash_v2[p];
}

void Push1(LL v, int x){
    int p = x % M;
    while(hash_n1[p])  p = (p+1) % M;
    hash_v1[p] = v;
    hash_n1[p] = x;
}

void Push2(int v, int x){
    int p = x % M;
    while(hash_n2[p])  p = (p+1) % M;
    hash_v2[p] = v;
    hash_n2[p] = x;
}

LL Sum_Phi(int x){
    if(x < N)  return phi[x];
    LL res = Find1(x);
    if(~ res)  return res;
    else  res = (LL)x * ((LL)x+1) >> 1;
    int last;
    for(int i = 2;; i = last+1){
      last = x/(x/i);
      res -= (LL)(last-i+1) * Sum_Phi(x/i);
      if(last >= x)  break;
    }
    Push1(res, x);
    return res;
}

int Sum_Miu(int x){
    if(x < N)  return miu[x];
    int res = Find2(x);
    if(~ res)  return res;
    else  res = 1;
    int last;
    for(int i = 2;; i = last+1){
      last = x/(x/i);
      res -= (last-i+1) * Sum_Miu(x/i);
      if(last >= x)  break;
    }
    Push2(res, x);
    return res;
}

int main(){

    freopen("bzoj3944.in", "r", stdin);
    freopen("bzoj3944.out", "w", stdout);

    Da();

    scanf("%d", &T);

    while(T --){
      scanf("%d", &n);
      printf("%lld %d\n", Sum_Phi(n), Sum_Miu(n));
    }

    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值