[BZOJ3944]Sum-杜教筛

Sum

Description

题面

Input

一共T+1行
第1行为数据组数T(T<=10)
第2~T+1行每行一个非负整数N,代表一组询问

Output

一共T行,每行两个用空格分隔的数ans1,ans2

Sample Input

6
1
2
8
13
30
2333

Sample Output

1 1
2 0
22 -2
58 -3
278 -3
1655470 2


很久以前,本蒟蒻很naive的认为 O(n) O ( n ) 的线性筛已经是最快的筛法了……
然而现在这个神奇的 O(n23) O ( n 2 3 ) 的杜教筛完全推翻了本蒟蒻的想法…….
大千世界真是无奇不有…….


思路:
杜教筛入门题。
杜教筛可以用来解决一些积性函数的前缀和问题。
主要是利用了这类积性函数的一些神奇的性质来简化求值……

咱语文水平不高所以上面两句毫无意义的话可以无视。
不如我们来推一波?
看例子想必是最容易理解的了,尤其是同时看两个。

φ(n): φ ( n ) :
对于 φ(n) φ ( n ) ,有一个性质: d|nφ(d)=n ∑ d | n φ ( d ) = n
我们可以把它转化成这样: φ(n)=nd|n,d<nφ(d) φ ( n ) = n − ∑ d | n , d < n φ ( d )

定义它的前缀和为 ϕ(n) ϕ ( n ) ,有:

ϕ(n)=ni=1φ(i) ϕ ( n ) = ∑ i = 1 n φ ( i )
=ni=1id|i,d<iφ(d) = ∑ i = 1 n i − ∑ d | i , d < i φ ( d )
=n(n+1)2ni=2d|i,d<iφ(d) = n ⋅ ( n + 1 ) 2 − ∑ i = 2 n ∑ d | i , d < i φ ( d )
=n(n+1)2ni=2nid=1φ(d) = n ⋅ ( n + 1 ) 2 − ∑ i = 2 n ∑ d = 1 ⌊ n i ⌋ φ ( d )
=n(n+1)2ni=2ϕ(ni) = n ⋅ ( n + 1 ) 2 − ∑ i = 2 n ϕ ( ⌊ n i ⌋ )

然后因为 ni ⌊ n i ⌋ 的取值对于一段连续的 i i 是相同的,咱就可以把相同的用乘法加速计算,而不是一个个去枚举了。

μ(n):
对于 μ(n) μ ( n ) ,同样有一个性质: [n=1]=d|nμ(d) [ n = 1 ] = ∑ d | n μ ( d )

定义它的前缀和为 M(n) M ( n ) ,有:

1=ni=1[i=1] 1 = ∑ i = 1 n [ i = 1 ]
=ni=1d|iμ(d) = ∑ i = 1 n ∑ d | i μ ( d )
=ni=1nid=1μ(d) = ∑ i = 1 n ∑ d = 1 ⌊ n i ⌋ μ ( d )
=ni=1M(ni) = ∑ i = 1 n M ( ⌊ n i ⌋ )

所以 M(n)=1ni=2M(ni) M ( n ) = 1 − ∑ i = 2 n M ( ⌊ n i ⌋ ) ,同样可以用 ni ⌊ n i ⌋ 的相同取值来加速。

于是这题就可做了,用哈希或map来一波记忆化搜索,就可以做到上面的 O(n23) O ( n 2 3 ) 了~

实现上,如果你和咱一样懒并且不怕自己的程序的时间复杂度多一个 log l o g ,尽管和咱一样开map,时间还是可以接受的(反正只是一个不算太大的值)~

另外会爆int,直接开long long或者int计算时强转都是可以的~

#include<iostream>
#include<map>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

typedef long long ll;
typedef map<int,ll>::iterator m_it;

const int N=5000000;

int pri[N/5],tot;
ll phi[N],mu[N];
bool npri[N];
int n;

map<int,ll>phis,mus;

inline void init()
{
    phi[1]=1;
    mu[1]=1;

    for(int i=2;i<N;i++)
    {
        if(!npri[i])
        {
            pri[++tot]=i;
            mu[i]=-1;
            phi[i]=i-1;
        }

        for(int j=1;j<=tot && pri[j]*i<N;j++)
        {
            npri[pri[j]*i]=1;

            if(i%pri[j])
            {
                phi[pri[j]*i]=(pri[j]-1)*phi[i];
                mu[pri[j]*i]=-mu[i];
            }
            else
            {
                phi[pri[j]*i]=phi[i]*pri[j];
                mu[pri[j]*i]=0;
                break;
            }
        }
    }

    for(int i=1;i<N;i++)
    {
        phi[i]+=phi[i-1];
        mu[i]+=mu[i-1];
    }
}

ll calc_phi(ll n)
{
    if(n<N)
        return phi[n];

    m_it it;
    if((it=phis.find(n))!=phis.end())
        return it->second;

    ll ret=n*(n+1)>>1,nxt;
    for(ll i=2;i<=n;i=nxt+1)
    {
        nxt=n/(n/i);
        ret-=(nxt-i+1)*calc_phi(n/i);
    }

    return phis[n]=ret;
}

ll calc_mu(ll n)
{
    if(n<N)
        return mu[n];

    m_it it;
    if((it=mus.find(n))!=mus.end())
        return it->second;

    ll ret=1,nxt;
    for(ll i=2;i<=n;i=nxt+1)
    {
        nxt=n/(n/i);
        ret-=(nxt-i+1)*calc_mu(n/i);
    }

    return mus[n]=ret;
}

int main()
{
    int T;
    scanf("%d",&T);

    init();
    while(T--)
    {
        scanf("%d",&n);
        printf("%lld %lld\n",calc_phi(n),calc_mu(n));
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值