bzoj4176 Lucas的数论 (杜教筛 +莫比乌斯反演)

83 篇文章 0 订阅
53 篇文章 0 订阅

bzoj4176 Lucas的数论

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=4176

题意:
去年的Lucas非常喜欢数论题,但是一年以后的Lucas却不那么喜欢了。

在整理以前的试题时,发现了这样一道题目“求Sigma(f(i)),其中1<=i<=N”,其中 表示i的约数个数。他现在长大了,题目也变难了。
求如下表达式的值:
这里写图片描述
其中 表示ij的约数个数。
他发现答案有点大,只需要输出模1000000007的值。

数据范围
n <= 10^9

题解:
d(nm)=injm[gcd(i,j)==1]
原式
=ni=1mj=1xiyj[gcd(x,y)==1]
=ni=1mj=1xiyjtxtyμ(t)
=nt=1μ(t)txxityyj1 (i,x,y,j<=n)
观察到后面两个式子的形式几乎完全一样,
=nt=1μ(t)(txxi1)2 (i,x<=n)
=nt=1μ(t)(ntxt=1nx)2
用i替换x/t,得:
=nt=1μ(t)(nti=1nti)2

定义, f(n)=ni=1ni
那么,
=nt=1μ(t) f(nt)2
前面对于 μ 的前缀和,有:

定义梅滕斯函数 M(n)=ni=1μ(i) ,使用 [n=1]=d|nμ(d)

1=i=1n[i=1]=i=1nd|iμ(d)=i=1nd=1niμ(d)=i=1nM(ni)

因此 M(n)=1ni=2M(ni) ,问题可在 O(n23) 时间复杂度下解决。

                                        ——摘自浅谈一类积性函数的前缀和
预处理 i<=n34 μ 前缀,更大的用上述用与 e() 的卷积推出来的东西,分块+缩小范围。
因为我们最后for的时候是按(n/i)分块一段一段地for,所以访问到的mu前缀和只会是 (μ(ni))
所以要预先处理n/i>S的 (μ(ni))
因为 n/i>S,所以i<=S,所以数组下标是按i保存。
对于后面 f(nt)2 的部分,分块计算 f(n)=ni=1ni
据PoPoQQQ大佬的博客,时间复杂度是 O(n1)+O(n2)+...+O(nn)=O(n34)
于是总复杂度 O(n34)

代码:

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int mod=1000000007;
const int N=10000007;
int n,S,mu[N],sum[N],p[N],ptot=0;
bool is[N];
void shai()
{
    memset(is,0,sizeof(is)); is[1]=1; mu[1]=1; 
    for(int i=2;i<=S;i++)
    {
        if(!is[i]) {p[++ptot]=i;mu[i]=-1;}

        for(int j=1;j<=ptot;j++)
        {
            int pp=p[j];
            if(1LL*pp*i>=1LL*N) break; 
            int x=pp*i; is[x]=1; mu[x]=mu[i]*mu[pp];
            if(i%pp==0) {mu[x]=0; break;}
        }
    }
    mu[0]=0;
    for(int i=1;i<=S;i++) mu[i]=(mu[i-1]+mu[i]+mod)%mod;
}
int cal(int x)
{
    int ans=0;
    for(int i=1,ed;i<=x;i=ed+1)
    {
        ed=(x/(x/i));
        ans=(ans+(1LL*(ed-i+1)*(x/i))%mod)%mod;
    }
    return ans;
}
int getmu(int x)
{
    if(x<=S) return mu[x];
    else return sum[n/x];
}
void init()
{
    shai();
    int top=1; while(n/top>S) top++;
    for(int j=top;j>=1;j--)
    {
        int now=n/j; sum[j]=1;
        for(int i=2,ed;i<=now;i=ed+1)
        {
            ed=now/(now/i); 
            sum[j]=(sum[j]-(1LL*(ed-i+1)*getmu(now/i))%mod+mod)%mod;
        }
    }   
}
int main()
{
    scanf("%d",&n); S=ceil(pow((double)n,0.75));

    init(); 
    int ans=0;
    for(int i=1,ed;i<=n;i=ed+1)
    {
        ed=n/(n/i); int tmp=cal((n/i));
        int ret=(getmu(ed)-getmu(i-1)+mod)%mod;
        ret=(1LL*ret*tmp)%mod; ret=(1LL*ret*tmp)%mod; 
        ans=(ans+ret)%mod;
    }
    printf("%d\n",ans);
    return 0;
}

补充:

1、 d() 为约数个数函数
d(nm)=injm[gcd(i,j)==1]
证明如下:
n,m的任意约数都可表示为 ij  (injm) 的形式
imj  (injm)
上式即是枚举 i,j

为什么必须 [gcd(i,j)==1] ?

i,j 都有因子 p
imjipmjp
会算重。

2、 abc=abc

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BZOJ 2908 题目是一个数据下载任务。这个任务要求下载指定的数据文件,并统计文件中小于等于给定整数的数字个数。 为了完成这个任务,首先需要选择一个合适的网址来下载文件。我们可以使用一个网络爬虫库,如Python中的Requests库,来帮助我们完成文件下载的操作。 首先,我们需要使用Requests库中的get()方法来访问目标网址,并将目标文件下载到我们的本地计算机中。可以使用以下代码实现文件下载: ```python import requests url = '目标文件的网址' response = requests.get(url) with open('本地保存文件的路径', 'wb') as file: file.write(response.content) ``` 下载完成后,我们可以使用Python内置的open()函数打开已下载的文件,并按行读取文件内容。可以使用以下代码实现文件内容读取: ```python count = 0 with open('本地保存文件的路径', 'r') as file: for line in file: # 在这里实现对每一行数据的判断 # 如果小于等于给定整数,count 加 1 # 否则,不进行任何操作 ``` 在每一行的处理过程中,我们可以使用split()方法将一行数据分割成多个字符串,并使用int()函数将其转换为整数。然后,我们可以将该整数与给定整数进行比较,以判断是否小于等于给定整数。 最后,我们可以将统计结果打印出来,以满足题目的要求。 综上所述,以上是关于解决 BZOJ 2908 数据下载任务的简要步骤和代码实现。 希望对您有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值